For example, the following piece of code would simply empty the text in the Display Screen:
DisplayScreen.Text := '';
However, what we want to do is add a number to the current string of text stored in the text property. This is done by using the '+' symbol to concatenate the two separate strings.
The best thing to do is to create a user-created procedure that handles updating the display screen when a number is clicked. This procedure will be called simply 'UpdateDisplay'. The first thing to do is add the declaration of this procedure to the <public declarations> section towards the beginning of the Pascal file:
public
procedure UpdateDisplay(Digit: String);
This declares the procedure and now it can be implemented as shown below:
procedure TForm1.UpdateDisplay(Digit: String);
begin
DisplayScreen.Text := DisplayScreen.Text + Digit;
end;
Notice the procedure takes a String called 'Digit' as a parameter. This is needed so that the specified number will be added. The code simply adds the digit value supplied to the existing text in the Display Screen.
The procedure will be called when a user clicks a number on the calculator, and the corresponding digit will need to be supplied. For example, when the user clicks 'OneBtn' (the button for the number one), the following code will need to be executed:
procedure TForm1.OneBtnClick(Sender: TObject);
begin
UpdateDisplay('1');
end;
Simple! The Display Value is simply updated. The UpdateDisplay can be easily used to then add any digit to the display screen and hence:
procedure TForm1.TwoBtnClick(Sender: TObject);
begin
UpdateDisplay('2');
end;
follows the same routine. This procedure is carried out for the OnClick event handler for all the 'number buttons' and then we have a calculator that at least does something now! Notice that the UpdateDisplay procedure is very simple indeed at the moment, and no conditions have to be met in order for the code that actually updates the text to be executed. This will invariably result in bugs here and there. Therefore, the procedure will most likely be revisited later when bugs are discovered and then fixed using an appropriate method.
The display screen must have a starting number of 1-9; 0 is not allowed. For example, 000454 is not valid, but 405400 is valid.
Already, some development on the original UpdateDisplay procedure is needed. I need to modify the procedure so that the number in the display screen does not begin with a 0. Otherwise we would end up having invalid numbers, and although the calculations may well still work, having numbers like 00483 rather than just 483 are not very accurate.
To solve this problem I'll simply modify the procedure call in the event handler of Button 0's OnClick procedure, so that it looks like the following:
procedure TForm1.ZeroBtnClick(Sender: TObject);
begin
if DisplayScreen.Text <> '0' then
UpdateDisplay('0');
end;
Here you can see that an 'if' statement has been added to the original statement. The 'if' statement simply checks to see that the value in the Display Screen is not 0, and if successful then adds 0 to the string of digits that are already in the Display Screen. However, if the string in the Display Screen's Text property is equal to '0', then the beneath statement is not carried out. This prevents the number 0 being added at the start of a number.
Text must not be able to be entered in the display screen
Calculations cannot be performed on text! Therefore, I have to devise a way so that text cannot be entered into the edit box (DisplayScreen). A relatively simple solution is to not allow the user to type anything into the edit box (including numbers) and just use the buttons instead. Setting the 'Read Only' property of the edit box to 'True' does the trick. This can be done from the Object Inspector.
When an arithmetic operator is pressed (+, -, * or /), then the corresponding arithmetic needs to be prepared
This is probably the most difficult problem in the whole of the implementation, although if I take it a step at a time and think logically, then it should be easy to come up with a reasonable solution. When a user clicks one of the operators, then the value that's been entered into the Display needs to be operated on with the chosen operator and the next value to be entered. For example, a user presses the button 2, and then selects 'Add'. At this point, the operator that's been selected needs to be 'remembered' in some way so that when another value followed by the equals button is pressed, the appropriate calculation is made. This isn't really as difficult as it sounds.
Firstly, let's consider the operator. When a user clicks an operator button, the type of operator needs to be stored. This can be stored in a String variable. Also, the number entered before the operator button is pressed needs to be held temporarily so that it's available for the calculation when the equals button/other arithmetic button is pressed. The second number entered also needs to be held temporarily, as well as the answer of the final calculation. So all in all, the following variables need to be declared in the Public Declarations section of the Pascal Unit:
public
x, y, Answer: Real;
Arithmetic: String;
x, y and Answer have all been defined as type Real, meaning that they are Real Numbers (that means they can include decimal points). This is much preferred over the standard Integer type as integers are whole numbers only, which limits the calculations performed by the calculator somewhat. The Arithmetic variable is declared as a string, which means a string of characters can be stored, e.g. 'Add', 'Multiply'. An enumeration could be set up here, but in this case using a single string variable as the arithmetic identifier will suffice.
Okay, so now when one of the arithmetic buttons is pressed, a block of code needs to be executed. The first thing in this block of code should be to check whether a valid value has been entered into the Display Screen. As this will need to be done for each operator's On Click event, it's worthwhile implementing the validity check as a separate procedure. We'll call it CheckIfValid. The procedure is declared in the normal way (added to Public Declarations), and the definition of the procedure is as following:
procedure TForm1.CheckIfValid;
begin
if DisplayScreen.Text = '' then DisplayScreen.Text := '0';
end;
Basically, all this does is check to make sure that a number has been entered before the code for the operator's On Click event is carried out. If a number hasn't been entered (i.e. the string is left empty) then it's set to 0 by default. This prevents a run-time error occur and also makes the calculator run more realistically.
The implementation of each operator button's On Click event is then as follows:
procedure TForm1.AddBtnClick(Sender: TObject);
begin
CheckIfValid;
x := x + StrToFloat(DisplayScreen.Text);
Arithmetic := 'Add';
end;
procedure TForm1.SubtractBtnClick(Sender: TObject);
begin
CheckIfValid;
x := x + StrToFloat(DisplayScreen.Text);
Arithmetic := 'Subtract';
end;
procedure TForm1.MultiplyBtnClick(Sender: TObject);
begin
CheckIfValid;
x := x + StrToFloat(DisplayScreen.Text);
Arithmetic := 'Multiply';
end;
procedure TForm1.DivideBtnClick(Sender: TObject);
begin
CheckIfValid;
x := x + StrToFloat(DisplayScreen.Text);
Arithmetic := 'Divide';
end;
Let's take a close look at this code, starting with the first block. The first step in each block calls the CheckIfValid procedure to make sure that a valid number is stored. Once the CheckIfValid procedure has ensured that a valid value has been made available then the next line of code is executed. In the AddBtnClick procedure, the temporary value, x, is assigned to the value already in x PLUS the real number equivalent of the string stored in the Display Screen. Setting x as x plus the number entered means that the ability to use several operators before actually finding the final calculation by pressing the Equals Button. The real number is obtained using the built-in System Utilities' StrToFloat procedure. Finally, the Arithmetic operator is set to 'Add' to specify that the arithmetic needed in the next step is to add. The pattern in each operator's On Click event is very similar; the only bit changing is the assignment of the Arithmetic variable in the bottom line according to the button clicked.
The arithmetic is now fully prepared for when the user wishes to perform a calculation.
The display screen also then needs to be cleared so that the next number can be entered
Once the above code has been carried out (i.e. a user click on the +,-,* or / button) then the Display Screen will need to be set to the default value of '0' ready for the next value to be entered. This is a very simple piece of code, which, again will be put into it's own procedure for sake of reusability:
procedure TForm1.ClearDisplay;
begin
DisplayScreen.Text := '0';
end;
The Display Screen is set to '0' ready for the next value to be entered. All that needs to be done to put this code into action is to call the procedure at the end of each operator button's On Click event. For example, the AddBtnClick event will now look like:
procedure TForm1.AddBtnClick(Sender: TObject);
begin
CheckIfValid;
x := x + StrToFloat(DisplayScreen.Text);
Arithmetic := 'Add';
ClearDisplay;
end;
Phew, that section's over, on to the next bit!
When the Equals Button is pressed, the arithmetic should be carried out accordingly
This is the part at which a calculation is performed and then the result output to the user. Firstly, the value that has been entered into the Display Screen is assigned to the temporary variable y. Then the appropriate simple calculation is performed depending on the value of the Arithmetic variable:
procedure TForm1.EqualsBtnClick(Sender: TObject);
begin
// Copies the value entered to y, a temporary storage
y := StrToFloat(DisplayScreen.Text);
// Carries out the arithmetic on the two variables x and y
if Arithmetic = 'Add' then
Answer := x + y;
if Arithmetic = 'Subtract' then
Answer := x - y;
if Arithmetic = 'Multiply' then
Answer := x * y;
if Arithmetic = 'Divide' then
Answer := x / y;
This is only a part of the procedure; there is more to come, but first let's concentrate on what we have so far. The temporary value, y, now holds the last entered value into the display screen. There are a group of four 'if' statements which now check to see which line of code needs to be executed. The same effect could be achieved using a case statement.
The if statements check to see which arithmetic needs to be performed then carries this out simply, assigning the result to the previously declared Answer variable. The following line of code will then be added, which actually shows the Answer value in the DisplayScreen, using the reverse FloatToStr procedure:
DisplayScreen.Text := FloatToStr(Answer);
end;
This is good, now the answer is displayed and we have a – what could be called – functional calculator.
However, after a few minutes fiddling with the calculator, it won't be long before you bump into some bugs that are only too happy to lurch around. When a user performs a calculation, this is fine and the correct answer is output. However, on a consequent calculation, the answer is way off and somewhat strange. This is directly because of the fact that the temporary values, x, y, and answer have not been cleared. These need to be set to 0 so that a calculation can be formed from fresh. Again, a handy little procedure is all we need:
procedure TForm1.ResetValues;
begin
x := 0;
y := 0;
Answer := 0;
end;
This procedure will actually need to be called in the UpdateDisplay procedure. This may seem unusual, but it's very logical. When the user has performed a calculation, the answer is then displayed in the Display Screen. The answer will need to stay there until the user clicks another button. However, with the answer stored in the Display Screen, it needs to be cleared before another number is entered. There is currently no way to find out whether the number in the Display Screen is the Answer or a number the user has entered. One way to find out is to declare a Boolean variable that keeps track of whether or not the Display needs clearing – I'll name the variable, NeedClearing. It should be added in the public declarations section. The reason why it 'needs' clearing is because the clearing cannot be done until the user enters a new number otherwise the answer wouldn't be available on screen.
Looking at the code, we can call the ClearDisplay procedure and then call the ResetValues procedure so as to refresh the contents of the temporary variables.
if NeedClearing = true then
begin
ClearDisplay;
ResetValues;
NeedClearing := false;
end;
This is the simple code that performs the objective outlined in the above (rather long) paragraph. It should be entered into the UpdateDisplay procedure in the second line of code, underneath the CheckIfValid procedure call.
Clicking the ‘C’ button must clear all current values and calculations in progress
A simple task of 'refreshing' the value in the display screen and any temporary values that are in use. As we have created earlier procedures to handle these tasks, it's a simple matter of making calls to the relevant procedures: ResetValue and ClearDisplay.
procedure TForm1.ClearBtnClick(Sender: TObject);
begin
ResetValues;
ClearDisplay;
end;
As can be seen, these two procedure calls are made from the ClearBtnClick event, which is when the Clear Button is clicked.
Clicking the ‘Bck’ button must delete the last number entered into the display screen
This is a rather fiddly task to deal with involving manipulation of strings. The string which we wish to manipulate it is held in DisplayValue's Text Property. When a user is entering a number they may make a mistake and so a click on the Backspace Button will usefully take them back one step. The first thing to worry about is how the program knows whether the string currently shown in the Display Screen is user-input or computer-output. It makes sense to only allow the backspace operation on user-inputted data. The best way to get around this problem is to use a Boolean variable to flag whether the data is UserEntered or not. This can be added to the public declarations section as per usual. Here is a list of all the variables we have declared so far:
public
x, y, Answer: Real;
Arithmetic: String;
UserEntered: Boolean;
procedure UpdateDisplay(Digit: String);
procedure CheckIfValid;
procedure EmptyDisplay;
procedure ClearDisplay;
procedure ResetValues;
Now that the Boolean variable, UserEntered, has been set up, it's time to put it to use. Every time the UpdateDisplay procedure is called, the user has input some data and hence
UserEntered := True;
This can be added to the end of the code block in the procedure. On the otherhand, when the EqualsBtnClick procedure is called, it means that an answer is being outputted by the computer and so
UserEntered := false;
Again, this can be tagged on at the end of the block of code. Now, when the user clicks the BckBtn, the Boolean variable can be tested as so:
procedure TForm1.BckBtnClick(Sender: TObject);
begin
if UserEntered = true then
begin
// Implementation Here
end;
end;
Now that this is sorted out, we have to actually take a character 'off' the string held in the Display Screen. This is just a matter of some fairly straightforward string manipulation. There is a built-in Delete procedure that allows the programmer to delete a sub-string from a string. The syntax of the procedure is as follows:
Declaration
procedure Delete(var S: string; Index, Count:Integer);
Description
The Delete procedure removes a substring of Count characters from string S starting at S[Index].
S is a string-type variable. Index and Count are integer-type expressions.
If Index is larger than the length of S, no characters are deleted. If Count specifies more characters than remain starting at the S[Index], Delete removes the rest of the string.
The first thing we need to do is declare a temporary string variable, and to keep it familiar with the syntax, we'll call it s. We then need to copy the contents of the Display Screen's string into this newly created s string:
procedure TForm1.BckBtnClick(Sender: TObject);
var
s: String;
begin
if UserEntered = true then
begin
s := DisplayScreen.Text;
end;
end;
So far, we've created a string and made it equal the string held in the Display. We are now able to freely manipulate this string. First, we need to figure out the index variable, i.e. the position in the string at which we want to delete a character. This happens to be right at the end of the string in this case, and is therefore the same to the number of characters held in the string, which can be found using the Length procedure. We'll store the result in an integer variable named Index which also needs to be declared in the local var section above the main block of code in this event procedure.
We then need to actually call the Delete procedure, passing s as the first parameter, Index as the second parameter and finally '1' as the last parameter. 1 is used to indicate that only 1 character is to be deleted from the string. That's not it; once we've manipulated the string we need to update the contents of the Display Screen with the new string. This is done by simply assigning DisplayValue.Text to s as can be seen below. The final step calls the CheckIfValid procedure so that if all the characters in the string are deleted then the value of the string is set to 0.
procedure TForm1.BckBtnClick(Sender: TObject);
var
s: String;
Index: Integer;
begin
if UserEntered = true then
begin
s := DisplayScreen.Text;
Index := Length(s);
Delete(s, Index, 1);
DisplayScreen.Text := s;
CheckIfValid;
end;
end;
Clicking the decimal point button needs to insert a decimal point at the current location in the number
Wahey, at last something to slow the heart beat down. This is a simple matter of adding the following code:
procedure TForm1.DecimalBtnClick(Sender: TObject);
begin
DisplayScreen.Text := DisplayScreen.Text + '.';
end;
However, as we'll see in the next section, it starts to get a bit more complicated than this.
Only one decimal point may be entered in a number
We can only have one decimal point in any one number. In order to implement this, we need to dig a little more into string manipulation. This time we're using another useful function, called Pos. This simply looks for a sub-string within a string. If the sub-string is found, then the function returns an integer value at the point where the sub-string was first found. If we carry out this function on the DisplayScreen.Text string, looking for the sub-string of '.' then we can find out whether or not the DisplayScreen already has a decimal point. If not, then one may be added:
procedure TForm1.DecimalBtnClick(Sender: TObject);
begin
if Pos('.', DisplayScreen.Text) = 0 then
DisplayScreen.Text := DisplayScreen.Text + '.';
end;
Clicking the positive/negative button must change the sign of the number to the opposite sign.
For this to work, we must first examine the numeric equivalent of the string and check to see if it is positive or negative. From there, the sign can be easily changed. Using the StrToFloat procedure to find the numeric value:
procedure TForm1.PosNegBtnClick(Sender: TObject);
var
Number: Real;
begin
Number := StrToFloat(DisplayScreen.Text);
if Number >= 0 then
Number := 0 - Number
else
Number := 0 - -Number;
DisplayScreen.Text := FloatToStr(Number);
end;
Finally, the basic workings of the calculator have been implemented and are working very well – no silly bugs that make the program unbearable to use (most of them have been ironed out so far) and it's generally a nice easy-to-use calculator.
Now, I'll take a look at the more advanced features of the calculator that I could implement, but won't be spending too long on this section due to lack of time.
Adding pi as a function button
This is really easy to tell the truth. The pi button has been added to the form and the OnClick event has been filled with the following:
procedure TForm1.PiBtnClick(Sender: TObject);
begin
ClearDisplay;
UpdateDisplay('3.141592654');
end;
Simply, the current display is cleared, and the value of pi is entered, allowing the user to interact with the value of pi as they so wish.
Again, really straightforward:
procedure TForm1.xsqdBtnClick(Sender: TObject);
begin
x := StrToFloat(DisplayScreen.Text);
Answer := x*x;
DisplayScreen.Text := FloatToStr(Answer);
ResetValues;
end;
procedure TForm1.sqrtBtnClick(Sender: TObject);
begin
x := StrToFloat(DisplayScreen.Text);
Answer := sqrt(x);
DisplayScreen.Text := FloatToStr(Answer);
ResetValues;
end;
This is a difficult part of the coding, not in implementing the features but in converting radians to degrees as needed. For now, I have left the answers as radians. If I had more time, I'd be able to convert them to degrees and may even consider doing so in a future version of the calculator. But for now, here's the final snippets of code:
procedure TForm1.SinBtnClick(Sender: TObject);
begin
ClearDisplay;
Arithmetic := 'sin';
end;
procedure TForm1.CosBtnClick(Sender: TObject);
begin
ClearDisplay;
Arithmetic := 'cos';
end;
procedure TForm1.TanBtnClick(Sender: TObject);
begin
ClearDisplay;
Arithmetic := 'tan';
end;
and planted in the EqualsBtnClick event procedure:
if Arithmetic = 'sin' then
begin
Answer := sin(StrToFloat(DisplayScreen.Text));
end;
if Arithmetic = 'cos' then
begin
Answer := cos(StrToFloat(DisplayScreen.Text));
end;
if Arithmetic = 'tan' then
begin
Answer := sin(StrToFloat(DisplayScreen.Text)) / cos(StrToFloat(DisplayScreen.Text));
end;
To convert to degrees, I would use the fact that 2pi is equal to 360 degrees, but for now radians will have to do!
Wayne Broadley
03:16 October 31 2000