Learn to Program with Delphi Community Edition: Part 5 - Putting the Calculator Together
Welcome to the last episode in the "Learn to Program with Delphi Community Edition" series. In the first four episodes we have covered "Introduction and Installation" of the free Delphi Community Edition, then we moved to IDE basics in "Building in Debugging". In "Architecture and Layers of Code" the key concepts of proper app structure were discussed and in the last episode we have started "Designing User Interfaces".
In the previous episode we have created the calculator project with the main form with 20 buttons and a label that acts as a "display". It is very important to separate user interface code from the application logic. The best way to achieve this separation is by using interfaces. However, This would add a little too much complexity for a starter series, so I'd rather stick to writing a separate class with the calculator logic.
First, we've added two new data types to the uCalculator unit. The first is an enumeration, as list of valid values for the "operation" of the calculator. The second is the actual class with the logic defined in a few methods and the status information, represented by a few fields, like any good class following object-oriented approach:
TOperatorEnum = (opNull, opAdd, opSubtract, opMultiply, opDivide); TCalcStatus = class private FInputValue: string; FCurrentTotal: Double; FOperation: TOperatorEnum; FDisplayTotal: Boolean; public constructor Create; procedure AddDigit(Digit: string); procedure AddDecimalSeparator; procedure CalcTotal; procedure NewOperation(Oper: TOperatorEnum); function DisplayValue: string; end;
The fields are used to store the current input string the user is typing, the total so far (from previous operations), the requested operation (that's going to be applied to next pair of values), and a Boolean flag indicating if the display needs to show the input value or the most recent total (something we'll do after the last operation but before the input starts again).
The methods have different roles. The two add operations, the NewOperator, and CalcTotal are involved directly when the various buttons are pressed in the UI. AddDigit would just pass the digit of the button that was presses, and NewOperation would do the same for the operation, triggering the calculation of the current input value. Here is their code:
procedure TCalcStatus.AddDecimalSeparator; begin FInputValue := FInputValue + FormatSettings.DecimalSeparator; end; procedure TCalcStatus.AddDigit (digit: string); begin FDisplayTotal := False; FInputValue := FInputValue + digit; end;
procedure TCalcStatus.NewOperation(Oper: TOperatorEnum); begin CalcTotal; FOperation := Oper; end;
The CalcTotal method is the most important one, triggered by pressing the = button of any of the operations. Notice how this code uses the TCalculator class we wrote in the step 3 of the series. It also resets the display status, so that the DisplayValue method can be skewed one way or the other:
procedure TCalcStatus.CalcTotal; var NewValue: Double; begin NewValue := StrToFloatDef(FInputValue, 0); case FOperation of opNUll: FCurrentTotal := NewValue; opAdd: FCurrentTotal := TCalculator.Add ( FCurrentTotal, NewValue); opSubtract: FCurrentTotal := TCalculator.Subtract( FCurrentTotal, NewValue); opMultiply: FCurrentTotal := TCalculator.Multiply( FCurrentTotal, NewValue); opDivide: FCurrentTotal := TCalculator.Divide ( FCurrentTotal, NewValue); end;
// reset status FOperation := opNull; FDisplayTotal := True; FInputValue := ''; end;
function TCalcStatus.DisplayValue: string; begin if FDisplayTotal then Result := FloatToStr(FCurrentTotal) else Result := FInputValue; end;
Wiring the Buttons to the Code
The final step is to make sure the form uses the TCalcStatus class and the buttons call the proper methods. But first we need to create an actual instance of this object. We can add the object to the form class, in the private fields section, and initialize / free it in two specific event handlers, OnCreate and OnDestroy. This is the complete definition of the form class, with all of the components and the event handlers, plus a custom field and a custom method. A form is just a class, so it can be extended like any other class in your code:
type TFormCalc = class(TForm) LayoutDisplay: TLayout; LabelDisplay: TLabel; GridLayoutButtons: TGridLayout; Button1: TButton; Button2: TButton; Button3: TButton; ButtonPlus: TButton; Button4: TButton; Button5: TButton; Button6: TButton; ButtonMInus: TButton; Button7: TButton; Button8: TButton; Button9: TButton; ButtonMultiply: TButton; ButtonDecimanl: TButton; Button14: TButton; ButtonEquals: TButton; ButtonDivide: TButton; procedure ButtonNumberClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ButtonDecimanlClick(Sender: TObject); procedure ButtonPlusClick(Sender: TObject); procedure ButtonMinusClick(Sender: TObject); procedure ButtonMultiplyClick(Sender: TObject); procedure ButtonDivideClick(Sender: TObject); procedure ButtonEqualsClick(Sender: TObject); private CalcStatus: TCalcStatus; public procedure Refresh; end;
procedure TFormCalc.FormCreate(Sender: TObject);
CalcStatus := TCalcStatus.Create;
procedure TFormCalc.FormDestroy(Sender: TObject);
For the 4 operations, the code is similar to the first OnClick event handler below. All operations are similar, but the event handlers are separate and so are the decimal separator and the = sign. For the numeric keys, instead, we've created a single event handler, hooked to all button (you do simply by selecting the a method for the event handler in the Object Inspector drop down list next to OnClick. This way the code is share and it relies on the "Sender" parameter, an object indicating which buttons was pressed, to access to the current button text (the number that was pressed). Here is the code:
procedure TFormCalc.ButtonDecimanlClick(Sender: TObject); begin CalcStatus.AddDecimalSeparator; Refresh; end; procedure TFormCalc.ButtonEqualsClick(Sender: TObject); begin CalcStatus.CalcTotal; Refresh; end; procedure TFormCalc.ButtonMultiplyClick(Sender: TObject); begin CalcStatus.NewOperation (opMultiply); Refresh; end; procedure TFormCalc.ButtonNumberClick(Sender: TObject); begin CalcStatus.AddDigit ((Sender as TButton).Text); Refresh; end; procedure TFormCalc.Refresh; begin LabelDisplay.Text := CalcStatus.DisplayValue; end;
Notice that almost each of the event handles includes a call to the Refresh method to update the UI. Ideally, the request to refresh the UI would come directly from the business logic via an interface, a nice future extension to this project.
Testing the Final Project
With all of this code written, we can now runt the final application and test it. I know it has some quirks I left in to avoid overcomplicating the code, but most of the basic operations work fine. Here is the application running:
What About Mobile?
For the sake of this initial series of blog posts meant to discover Delphi Community Edition, we have only focused on Windows. But this application can be easily recompiled for mobile platforms, something we'll focus on a follow up blog post.
This is for now the end of the series, and here are the links to the previous posts. More will be added soon.
Part 1: Introduction and Installation
Part 2: Building and Debugging in Delphi
Part 3: Architecture and Layers
Part 4: Designing User Interfaces
Part 5: Putting the Calculator Together (this post)
Saturday, 15 September 2018
I was pretty excited to begin my journey with Object Pascal and Delphi until I hit the brick wall known as Part 5. As this tutorial is linked to from the "Welcome to Delphi Community Edition!" email, I really recommend someone at Embarcadero go through this tutorial step-by-step and figure out what's missing. This is going to give newbies a bad first experience and I don't think that's the intention!
Friday, 20 July 2018
I've gotten thru this lesson just fine until this final section. I added the code snippet with the "type TOperatorEnum = (opNull, opAdd, opSubtract, opMultiply, opDivide);" successfully to the uCalculator unit with no errors. But the rest of the code snippets don't really say what unit to add the code to. I tried adding it into the uCalculator unit but wound up with tons of errors and warnings. So where (in what unit) do these snippets get added to? I'm a noob to Delphi. Thank you for any help you can give!
Wednesday, 25 July 2018
There are a couple problems with this article. The big one is that he doesn't discuss the uses section of a unit. By adding uCalculator to the uses section of uFormCalculator, I have been able to fix many of the errors. Also, you need to add to uCalculator, under interface "uses System.SysUtils;". The only error that remains after knowing to make those changes is how to write uCalculator.TCalcStatus.Create. I am not sure exactly what that constructor should look like. Another issue throughout this series is a lack of editing. It is plagued with grammatical errors that would really help with the readability of the series. There is one point where he even mentions 20 buttons though there are only 16.
Please login first in order for you to submit comments
- Page :
nice, thanks for this tutorial,
got it working at 38:41 minutes with the help of a remark here.
some button procedures was missing.
in unit uFormCalculator at uses need the add of ,uCalculator
at unit uCalculator i remark this // constructor Create;
today i used delphi 10.2 comminity version