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 5: Putting the Calculator Together (this post)
Please login first in order for you to submit comments
- Page :