You are on page 1of 13

1

CHAPTER 13

INTRODUCING DELPHI PROGRAMMING


Theory through Practise
CHAPTER 13

Contents
Chapter 13 - Functions and Exceptions ..................................................................................................... 2
Problem 13.1 - Safe spraying conditions ................................................................................................... 2
Problem 13.2 - Mortgage bond application ............................................................................................... 4
Problem 13.3 - Converting a procedure to a function................................................................................ 6
Problem 13.4 - Procedure/function criteria (1) .......................................................................................... 8
Problem 13.5 - Procedure/function criteria (2) .......................................................................................... 8
Problem 13.6 - Improved validation for Mr Kite..................................................................................... 10
Problem 13.7 - Convert a procedure to a function ................................................................................... 12

CHAPTER 13

Chapter13FunctionsandExceptions
A possible solution for each problem appears below. These solutions mostly apply to the third edition too.
Differences are mentioned in the text at the appropriate points.

Problem13.1Safesprayingconditions
In terms of properties, lblDecisions Font.Size property needs to be increased, say in the range of 20pt to 24 pt.
These methods (general functions) need to test particular conditions. For this, one can use an If statement as
demonstrated in the question by the TempOK function. One can also use an assignment statement involving a
Boolean expression. The solutions for the 3rd edition use the If statements while the solutions for the 4th edition
use the assignment technique.
3rd edition: This illustrates using the If statements:
function TempOK: Boolean;
begin
if frmSpraying.sedTemp.Value > 20 then
Result := True
else
Result := False;
end; {end function TempOK}
function WindOK: Boolean;
begin
if frmSpraying.sedWind.Value <= 15 then
Result := True
else
Result := False;
end; {end function WindOK}
function HumidOK: Boolean;
begin
if (frmSpraying.sedHumid.Value > 25) and
(frmSpraying.sedHumid.Value < 65) then
Result := True
else
Result := False;
end; {end function HumidOK}
procedure TfrmSpraying.btnEvaluateClick(Sender: TObject);
begin
// as given in the question
end; { procedure TfrmSpraying.btnEvaluateClick }
procedure TfrmSpraying.bmbResetClick(Sender: TObject);

CHAPTER 13

begin
sedTemp.Value := 0;
sedWind.Value := 0;
sedHumid.Value := 0;
lblDecision.Caption := '';
lblDecision.Color := clBtnFace;
sedTemp.SetFocus;
end; { procedure TfrmSpraying.bmbResetClick }

4th edition: This illustrates using the assignment statements:


function TfrmSpraying.TempOK: Boolean;
begin
Result := (sedTemp.Value > 20);
end; {end function TfrmSpraying.TempOK}
function TfrmSpraying.WindOK: Boolean;
begin
Result := (sedWind.Value <= 15);
end; {end function TfrmSpraying.WindOK}
function TfrmSpraying.HumidOK: Boolean;
begin
Result := (sedHumid.Value > 25) and (sedHumid.Value < 65);
end; {end function TfrmSpraying.HumidOK}
procedure TfrmSpraying.btnEvaluateClick(Sender: TObject);
begin
// as given in the question
end; { procedure TfrmSpraying.btnEvaluateClick }
procedure TfrmSpraying.bmbResetClick(Sender: TObject);
begin
sedTemp.Value := 0;
sedWind.Value := 0;
sedHumid.Value := 0;
lblDecision.Caption := '';
lblDecision.Color := clBtnFace;
sedTemp.SetFocus;
end; { procedure TfrmSpraying.bmbResetClick }

With the 4th edition, because we are writing methods attached to the form, we must also declare each method as
part of the forms type declaration:
type

CHAPTER 13

TfrmSpraying = class(TForm)
// list of components and event handlers as generated by Delphi
private
{ Private declarations }
function TempOK: Boolean;
function WindOK: Boolean;
function HumidOK: Boolean;
public
{ Public declarations }
end;

Enter the full version of the program and then check that it works correctly by using the test cases below. For the
test cases we need to check that each of the three factors is taken into account. How would we do this? We need
in turn to set two factors to valid values and then vary the third between valid and invalid values to see the effect.
We should also attempt to test each factor at its borderline values.
What are the borderline values? The temperature must be greater than 20C, so at 20C there should not be
spraying while at 21C spraying is OK. The humidity must be between 25 and 65 percent, so 25% and 65%
are outside the limits. The wind speed must be at most 15 kph, so 15kph is OK while 16kph is not. On this
basis, the minimum set of test cases is as follows, though one might do some additional ones:
Test case
1
2
3
4
5
6
7
8

Temperature
30
30
30
30
30
30
20
21

Wind speed
10
10
10
10
15
16
10
10

Humidity
25
26
64
65
45
45
45
45

Outcome
No
OK
OK
No
OK
No
No
OK

Problem13.2Mortgagebondapplication
The function to evaluate whether or not the applicant meets the acceptance criteria can use eithr an If statements
or an assignment statement to a Boolean expression. As in the previous solution, we give both these versions
here. The 3rd edition version uses the assignment statement and the 4th edition version uses the If statement.
3rd edition: This illustrates using the assignment statement in the ApplyOK function:
function ApplyOK: Boolean;
begin
Result := (frmBond.sedAge.Value >= 18) and
(frmBond.sedIncome.Value >= 24000) and
(frmBond.sedIncome.Value <= 60000);
end; // end function ApplyOK

CHAPTER 13

4th edition: This illustrates using the If statement in the ApplyOK method. Because this is being declared as a
method, its declaration must appear in the forms type declaration.
type
TfrmBond = class(TForm)
// list of components and event handlers as generated by Delphi
private
{ Private declarations }
function ApplyOK : Boolean;
public
{ Public declarations }
end; // end TfrmBond = class(TForm)
function TfrmBond.ApplyOK : Boolean;
begin
if (sedAge.Value >= 18) and (sedIncome.Value >= 24000)
and (sedIncome.Value <= 60000) then
Result := True
else
Result := False;
end; // end function TfrmBond.ApplyOK : Boolean

The event handlers are the same whether one uses the method approach of the 4th edition or the general function
approach of the 3rd edition:
procedure TfrmBond.bmbEvaluateClick(Sender: TObject);
begin
if ApplyOK then
lblOutcome.Caption := 'Accept'
else
lblOutcome.Caption := 'Reject';
end; // end procedure TfrmBond.bmbEvaluateClick
procedure TfrmBond.bmbResetClick(Sender: TObject);
begin
sedIncome.Value := 0;
sedAge.Value := 0;
lblOutcome.Caption := '';
sedIncome.SetFocus
end; // end procedure TfrmBond.bmbResetClick

A suitable set of test cases is:


Test case
1
2
3

Income
42000
42000
23000

Age
17
18
25

Outcome
Reject
Accept
Reject

Test case
4
5
6

Income
24000
60000
61000

CHAPTER 13

Age
25
25
25

Outcome
Accept
Accept
Reject

Problem13.3Convertingaproceduretoafunction
For convenience, we repeat the relevant code of prob 12.3 (using the procedure as a method):
procedure TfrmJJCircus.AdmissionPrice (Adult, Child, Discount: Double;
out CostStr: string);
const AdultCost = 9.90; // Local constants
ChildCost = 6.60;
var
Cost : Double; // Local variable
begin
Adult := Adult * AdultCost; // Note - value not constant parameters!!
Child := Child * ChildCost;
Cost := (1 - Discount) * (Adult + Child);
CostStr := FloatToStrF (Cost, ffCurrency, 12, 2);
CostStr := 'Admission price: ' + CostStr;
end; // end procedure TfrmJJCircus.AdmissionPrice
procedure TfrmJJCircus.btnWeekendClick(Sender: TObject);
var
PriceStr : string;
begin
AdmissionPrice (sedAdult.Value, sedChild.Value, 0.0, PriceStr);
lblPrice.Caption := PriceStr;
sedAdult.SetFocus;
end; // end procedure TfrmJJCircus.btnWeekendClick
procedure TfrmJJCircus.btnWeekDayClick(Sender: TObject);
var
PriceStr : string;
begin
AdmissionPrice (sedAdult.Value, sedChild.Value, 0.1, PriceStr);
lblPrice.Caption := PriceStr;
sedAdult.SetFocus;
end; // end procedure TfrmJJCircus.btnWeekDayClick

Lets start by converting the methods declaration (the procedures header), which is:
procedure AdmissionPrice (Adult, Child, Discount: Double;
out CostStr: string);

CHAPTER 13

to a function declaration (header) as follows:


function AdmissionPrice (Adult, Child, Discount: Double): string;

We keep the same name and the same ingoing parameters. The single outgoing value, instead of being an Out
parameter, is returned through the name of the method (function). So the method (function) must have the same
type, string, as the Out parameter of the procedure. Lets now change the call. In lines 19 and 27 above we see
typical procedure calls. Functions calls often occur as part of an assignment statement. So btnWeekends
OnClick handler becomes:
procedure TfrmJJCircus.btnWeekendClick(Sender: TObject);
var
PriceStr : string; // local variable
begin
PriceStr := AdmissionPrice (sedAdult.Value, sedChild.Value, 0.0);
lblPrice.Caption := PriceStr;
sedAdult.SetFocus;
end; // end procedure TfrmJJCircus.btnWeekendClick

Should we want to, we can simplify this even further by doing away with the local variable:
procedure TfrmJJCircus.btnWeekendClick(Sender: TObject);
begin
lblPrice.Caption := AdmissionPrice (sedAdult.Value, sedChild.Value, 0.0);
sedAdult.SetFocus;
end; // end procedure TfrmJJCircus.btnWeekendClick
The same approach applies to btnWeekDays OnClick event handler. Now lets return our attention to the
function itself, and make the necessary changes to the function body. Since we no longer have an Out parameter
supplying the value to the calling statement, we must replace all the assignments (lines 11&12 above) with an
assignment to the functions automatic Result variable:
function AdmissionPrice (Adult, Child, Discount: Double): string;
const AdultCost = 9.90; {Local constants}
ChildCost = 6.60;
var
Cost: Double; {Local variable}
begin
Adult := Adult * AdultCost;
Child := Child * ChildCost;
Cost := (1 - Discount) * (Adult + Child);
Result := FloatToStrF (Cost, ffCurrency, 12, 2); //automatic Result variable
Result := 'Admission price: ' + Result;
end; // end function AdmissionPrice

Assigning a value to the Result variable ensures that the function name returns a value in the calling statement.

CHAPTER 13

Problem13.4Procedure/functioncriteria(1)
Procedure Convert in problem 12.4 returns two values, the Out parameters AmtStr and CommStr. A function
returns a single value through its name, and so in terms of good programming practice, Convert should remain a
procedure. In terms of not-so-good programming practice, we could use a function by using the function name to
return one of the values, say AmtStr, and keeping the other in the parameter list as an Out or Variable parameter.
But this is not good style and should only be done if there are very good reasons in favour of it.

Problem13.5Procedure/functioncriteria(2)
There are two procedure type methods (3rd edition: general procedures) in the final version of prob 12.7, and
these are candidates for conversion to functions. (The Event handlers are also procedures, but by their nature
cannot be functions.) A good place to start is by looking at the declarations (3rd edition: headers) of the two
general procedures:
procedure DetermineDetails (out Loan, Rate: Double; out Payments: Integer);
procedure CalcRepay (Loan, Rate: Double; NoOfPeriods: Integer;
out Instalment: Double);
DetermineDetails has three Out parameters, and so is better left as a procedure. (This is often the case with
routines dealing with the user interface.) CalcReplay has only one Out parameter and it performs a specific
calculation. So, using the criteria of chapter 13, it is an ideal candidate for a function. We follow similar steps to
prob 13.3, and note that all of CalcRepays can be constant parameters. Listing only the changed routines, we
arrive at:

3rd edition:
function CalcRepay (const Loan, Rate: Double;
const NoOfPeriods: Integer): Double;
var Factor : Double;
begin
Factor := (1 - exp (-NoOfPeriods * ln (1.0 + Rate)));
Result := Loan * Rate / Factor;
end; // end function CalcRepay

4th edition: First we must modify the type declaration:


type
TfrmLoan = class(TForm)
// list of components and event handlers as generated by Delphi
private
{ Private declarations }
function CalcRepay (const Loan, Rate: Double;
const NoOfPeriods: Integer): Double;
procedure DetermineDetails (out Loan, Rate: Double;
out Payments : Integer);
public
{ Public declarations }
end; // end TfrmLoan = class(TForm)

Then we change CalcRepay to a function-type method:


function TfrmLoan.CalcRepay (const Loan, Rate: Double;
const NoOfPeriods: Integer): Double;
var Factor : Double;
begin
Factor := (1 - exp (-NoOfPeriods * ln (1.0 + Rate)));
Result := Loan * Rate / Factor;
end; // end function TfrmLoan.CalcRepay

Using these functions is the same in either approach:


procedure TfrmLoan.bmbCalculateClick(Sender: TObject);
var NoOfPayments : Integer;
Rate, Loan, Repayment : Double;
begin
{Get loan details}
DetermineDetails (Loan, Rate, NoOfPayments);
{Calculate monthly repayment}
Repayment := CalcRepay (Loan, Rate, NoOfPayments);
{Display monthly repayment}
lblRepay.Caption := 'Monthly repayment is '
+ FloatToStrF (Repayment, ffCurrency, 12, 2);
end; // end procedure TfrmLoan.bmbCalculateClick

CHAPTER 13

10

CHAPTER 13

Problem13.6ImprovedvalidationforMrKite
Printing errors in the textbook: In the 3rd edition, the final test case gives code 302 as returning True. This is
wrong. 302 returns False. This code should be 384 to return True. In the first printing of the 4th edition, the first
part of the problem correctly refers to examples 13.2 and 13.3. However, part (a) of the question refers to
example 13.5 step 3. This should be example 13.3 step 3.
a

Changing the validation code as requested involves a change only to function Validate:

3rd edition:
function Validate (const a, b, c: integer): boolean;
var
Sum: Integer;
begin
Sum := ((103*a + 17*b + c) mod 43) - 19;
Result := (Sum = 0);
end; // function Validate

4th edition:
type
TfrmAccess = class(TForm)
// list of components and event handlers as generated by Delphi
private
{ Private declarations }
procedure ReadCode (out A, B, C: integer);
procedure ResetUI;
procedure ResetStatus;
procedure ShowAccessStatus (const Status: boolean);
function Validate (const a, b, c: integer): boolean;
public
{ Public declarations }
end; // end TfrmAccess = class(TForm)
function TfrmAccess.Validate(const a, b, c: integer): boolean;
var
Sum: Integer;
begin
Sum := ((103*a + 17*b + c) mod 43) - 19;
Result := (Sum = 0);
end; // end function TfrmAccess.Validate

For either of these versions, the statement:

11

CHAPTER 13

Result := (Sum = 0);


can be replaced by:
if Sum = 0 then
Result := True
else
Result := False;

If we now test this version of Validate against the test cases supplied, we find that 012 and 384 give True and
that 123 and 279 give false, which is as given in the question. (In the 3rd edition, the value 302 should be
replaced by 384 as mentioned above.)
b
This new version of the Validate function can be substituted directly in examples 13.2 and 13.3 since it
keeps the same name and set of parameters. This illustrates that if we change the internal operation of a method
(subroutine) but keep its interface (ie the methods or subroutines type, name and parameter list) the same, the
overall program will continue to operate without being aware of the change. However, even though this new
version of Validate can be plugged in to a previous version of the program, it will now respond differently to
the codes entered.
4th edition: The 4th edition adds parts c & d to this question.
c
Here we must add a timer to reset the interface automatically after the Status display has been on display
for two seconds. Add a Timer to the form. Using the Object Inspector, change its Name to tmrResetStatus, its
Interval to 2000, and the Enabled property to False. Whenever this timers OnTimer event handler fires, it must
reset the user interface and disable itself:
procedure TfrmAccess.tmrResetStatusTimer(Sender: TObject);
begin
frmAccess.Color := clBtnFace;
lblStatus.Caption := 'Enter Code';
edtCode.Clear;
edtCode.SetFocus;
tmrResetStatus.Enabled := False;
end; // end procedure TfrmAccess.tmrResetStatusTimer

The timer must be enabled whenever the user clicks the OK button, so add the following statement as the last
statement in bmbOKs OnClick event handler:
tmrResetStatus.Enabled := True;

d
In this final part we must generate a list of all the valid codes for the new validation method given in
problem 13.6. There are different ways to go about this. We decided, just for test purposes, to add a Button
(btnTest) and a ListBox (lstTest) to the original user interface. Clicking btnTest then displays a list of all the
valid codes in lstTest. We add an OnClick event handler for btnTest:

12

CHAPTER 13

procedure TfrmAccess.btnTestClick(Sender: TObject);


var
A, B, C: integer;
begin
for A := 0 to 9 do
for B := 0 to 9 do
for C := 0 to 9 do
if Validate (A, B, C) then
lstTest.Items.Add(IntToStr(A) + IntToStr(B) + IntToStr(C));
end; // end procedure TfrmAccess.btnTestClick

Here we re-use the Validate method (function) that we defined at the start of this problem. This also gives good
revision for the concept of a nested loop.

Problem13.7Convertaproceduretoafunction
This problem is new to the 4th edition.
type
TfrmMarkUp = class(TForm)
// list of components and event handlers as generated by Delphi
private
{ Private declarations }
function SellingPrice (const Cost: string;
const Markup: double): string;
public
{ Public declarations }
end; // end TfrmMarkUp = class(TForm)
var
frmMarkUp: TfrmMarkUp;
implementation
{$R *.dfm}
function TfrmMarkUp.SellingPrice(const Cost: string;
const Markup: double): string;
var
Wholesale, Selling : double;
begin
Wholesale := StrToFloat (Cost);
Selling := Wholesale + Markup * Wholesale; // add markup
Result := FloatToStrF (Selling, ffCurrency, 15, 2);
end; // end function TfrmMarkUp.SellingPrice

13

procedure TfrmMarkUp.spdFifteenClick(Sender: TObject);


begin
lblSelling.Caption := SellingPrice (edtWholesale.Text, 0.15);
end; // procedure TfrmMarkUp.spdFifteenClick
procedure TfrmMarkUp.spdTwentyClick(Sender: TObject);
begin
lblSelling.Caption := SellingPrice (edtWholesale.Text, 0.2);
end; // end procedure TfrmMarkUp.spdTwentyClick
procedure TfrmMarkUp.spdTwentyFiveClick(Sender: TObject);
begin
lblSelling.Caption := SellingPrice (edtWholesale.Text, 0.25);
end; // end procedure TfrmMarkUp.spdTwentyFiveClick
procedure TfrmMarkUp.bmbResetClick(Sender: TObject);
begin
edtWholesale.Clear;
// clear wholesale price
lblSelling.Caption := ''; // clear selling price
edtWholesale.SetFocus;
spdFifteen.Down := False; // reset 15% button
end; // end procedure TfrmMarkUp.bmbResetClick
end.

// end unit C13p07u

CHAPTER 13