Learn to Program with Delphi Community Edition: Part 5 - Putting the Calculator Together

Posted by on in Blogs

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".

Application Logic

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:

type
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);
begin
CalcStatus := TCalcStatus.Create;
end;

procedure TFormCalc.FormDestroy(Sender: TObject);
begin
CalcStatus.Free;
end;

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.

What's Next

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)

 



About
Gold User, Rank: 7, Points: 457
Delphi and RAD Studio Product Manager at Embarcadero.

Comments

  • NoErrorsFound
    NoErrorsFound 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!

  • Larhop
    Larhop 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!

  • LandAbu0527
    LandAbu0527 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.

  • LandAbu0527
    LandAbu0527 Wednesday, 25 July 2018

    It would also be nice if there were an edit option on comments. My second to the last sentence should have read, "It is plagued with grammatical errors that fixing would really help with the readability of the series."

  • Please login first in order for you to submit comments
  • Page :
  • 1

Check out more tips and tricks in this development video: