Menu

Tradução

Português English Español Français

Clone da Calculadora do Windows em Delphi: Tutorial Passo a Passo

Nesse post iremos desenvolver um clone da calculadora do Windows em Delphi.

 

Clone da Calculadora do Windows em Delphi
Calculadora do Windows

Introdução

A criação de uma aplicação simples como uma calculadora é um excelente ponto de partida para aprender os conceitos de programação orientada a objetos em Delphi. Neste artigo, vamos desenvolver um clone da calculadora do Windows em Delphi, utilizando orientação a objetos. Vamos explorar o código detalhadamente para que você possa entender cada parte do desenvolvimento. Mas não se engane, apesar de ser um exemplo simples, a calculadora possui operações matemáticas mais avançadas em comparação com as aprendidas anteriormente no projeto de calculadora simples.

Estrutura do Projeto

Vamos criar uma aplicação Delphi com os seguintes componentes principais:

  • Um formulário principal (TfrmCalculator).
  • Botões para números (0-9) e operações (+, -, x, ÷, %, +/-, ⅟x, x² e ²√x).
  • Um botão de igual (=) para calcular o resultado.
  • Um botão de limpar (C) para reiniciar a calculadora.
  • Um botão para cancelar a entrada (CE) do número digitado.
  • Um botão Õ para apagar o último número digitado.
  • Um rótulo (TLabel) para exibir os resultados e operações.

Passo a Passo

Passo 1: Configuração Inicial

  1. Crie um novo projeto VCL no Delphi:
    • Selecione File > New > VCL Forms Application - Delphi.
    • Salve o projeto como prjCalculator.
    • Salve a unit do formulário como untCalculator e o form como frmCalculator.
    • Adicione uma nova unit e salve como untClassCalculator, é nessa unit que ficará a classe responsável pelos cálculos da calculadora.
  2. Adicione Componentes ao Formulário:
    • Adicione um TLabel no topo do formulário para exibir os números e resultados.
    • Adicione botões (TButton) para números de 0 a 9, operações (+, -, x, ÷, %, +/-, ⅟x, x² e ²√x), igual (=) , cancelar a entrada (CE) , limpar (C) e apagar (Õ).

Passo 2: Definindo a Classe da Calculadora

Vamos criar uma classe para gerenciar as operações da calculadora.

Delphi

type  
  TCalculator = class
  private
    FCurrentValue: Double;
    FCurrentValueStr: String;
    FStoredValue: Double;
    FCurrentOperation: string;
    FDecimalEntered: Boolean;
    FEqualEntered: Boolean;
    FAccumulated: Boolean;
    procedure SetCurrentValue(const Value: Double);
    procedure SetCurrentValueStr(const Value: String);
    function DecimalPlaces(Number: String): String;
    function FormatNumberToFloat(Number:String):String;
  public
    constructor Create;
    procedure Clear;
    procedure AddDigit(Digit: string);
    procedure Calculate;
    procedure CalculateAddition;
    procedure CalculateSubtraction;
    procedure CalculateMultiplication;
    procedure CalculateDivision;
    procedure CalculatePercent;
    procedure CalculateSquareRoot;
    procedure CalculateInverse;
    procedure CalculatePower;
    procedure AddDecimal;
    procedure DeleteLastDigit;
    procedure InvertSign;
    procedure CancelEntry;
    procedure SetCurrentOperation(const Value: String);
    property CurrentValue: Double read FCurrentValue write SetCurrentValue;
    property CurrentValueStr : String read FCurrentValueStr write SetCurrentValueStr;
    property CurrentOperation : String read FCurrentOperation write SetCurrentOperation;
  end;
  
Implementação da Classe TCalculator
Delphi

constructor TCalculator.Create;
begin
  Clear;
end;

procedure TCalculator.AddDecimal;
begin
  if not FDecimalEntered then
    begin
      FDecimalEntered := True;
      if Pos(',', FCurrentValueStr) = 0 then
        FCurrentValueStr := FCurrentValueStr + ',';
    end;
end;

procedure TCalculator.AddDigit(Digit: string);
var
  valueStr : String;
begin
  if FEqualEntered and (FCurrentOperation = '') then
    Clear;

  if ((FCurrentValue = 0) or FAccumulated) and (not FDecimalEntered) then
    begin
      FCurrentValue := StrToFloat(Digit);
      FCurrentValueStr := Digit;
      FAccumulated := False;
    end
  else
    begin
      valueStr := FCurrentValueStr + Digit;
      FCurrentValueStr := FormatNumberToFloat(valueStr);
      FCurrentValue    := StrToFloat(StringReplace(FCurrentValueStr,'.','',[rfReplaceAll])); //Remove os pontos, deixando somente número com casas decimais
    end;
end;

procedure TCalculator.Calculate;
begin
  if FCurrentOperation = '+' then
    FCurrentValue := FStoredValue + FCurrentValue
  else
  if FCurrentOperation = '-' then
    FCurrentValue := FStoredValue - FCurrentValue
  else
  if FCurrentOperation = '*' then
    FCurrentValue := FStoredValue * FCurrentValue
  else
  if FCurrentOperation = '^' then
    FCurrentValue := Power(FStoredValue, 2)
  else
  if FCurrentOperation = '2' then
    FCurrentValue := Sqrt(FStoredValue)
  else
  if FCurrentOperation = '%' then
    FCurrentValue :=  FStoredValue + ((FStoredValue * FCurrentValue) / 100)
  else
  if FCurrentOperation = '/' then
    begin
      if FCurrentValue > 0 then
        FCurrentValue := FStoredValue / FCurrentValue
      else
        raise Exception.Create('Divisão por zero não permitida');
    end
  else
  if FCurrentOperation = 'i' then
    begin
      if FStoredValue > 0 then
        FCurrentValue := 1 / FStoredValue
      else
        raise Exception.Create('Divisão por zero não permitida');
    end;

  FCurrentValueStr := FormatNumberToFloat(FloatToStr(FCurrentValue));

  FCurrentOperation := '';
  FEqualEntered := True;
end;

procedure TCalculator.CalculateDivision;
begin
  SetCurrentOperation('/');
end;

procedure TCalculator.CalculateAddition;
begin
  SetCurrentOperation('+');
end;

procedure TCalculator.CalculateInverse;
begin
  SetCurrentOperation('i');
end;

procedure TCalculator.CalculateMultiplication;
begin
  SetCurrentOperation('*');
end;

procedure TCalculator.CalculatePercent;
begin
  FCurrentOperation := '%';

  if FStoredValue = 0 then
  begin
    Calculate;
    FCurrentOperation := '';
  end;
end;

procedure TCalculator.CalculatePower;
begin
  SetCurrentOperation('^');
end;

procedure TCalculator.CalculateSquareRoot;
begin
  SetCurrentOperation('2');
end;

procedure TCalculator.CalculateSubtraction;
begin
  SetCurrentOperation('-');
end;

procedure TCalculator.CancelEntry;
begin
  FCurrentValue := 0;
  FCurrentValueStr := '0';
  FDecimalEntered := False;
  FEqualEntered := False;
end;

procedure TCalculator.Clear;
begin
  FCurrentValue := 0;
  FCurrentValueStr := '0';
  FStoredValue := 0;
  FCurrentOperation := '';
  FDecimalEntered := False;
  FEqualEntered := False;
  FAccumulated := False;
end;

procedure TCalculator.DeleteLastDigit;
begin
  if FEqualEntered then
    Exit;

  if Length(FCurrentValueStr) > 0 then
    begin
      CurrentValueStr := Copy(FCurrentValueStr, 1, Length(FCurrentValueStr) - 1);
      if CurrentValueStr = '' then
        begin
          FCurrentValue := 0;
          FCurrentValueStr := '0';
        end
      else
        FCurrentValue := StrToFloat(StringReplace(FCurrentValueStr,'.','',[rfReplaceAll]));

       CurrentValueStr := FormatNumberToFloat(FCurrentValueStr);
    end;
end;

function TCalculator.FormatNumberToFloat(Number: String): String;
var
  doubleNumber : Double;

begin
  doubleNumber := StrToFloat(StringReplace(Number,'.','',[rfReplaceAll]));
  Result := FormatFloat('#,##0.'+DecimalPlaces(Number),doubleNumber);
end;

procedure TCalculator.InvertSign;
begin
  FCurrentValue := -FCurrentValue;
  FCurrentValueStr := FormatNumberToFloat(FloatToStr(FCurrentValue));
end;

procedure TCalculator.SetCurrentOperation(const Value: String);
begin

  if (not FEqualEntered) and (FStoredValue <> 0) and (FCurrentValue <> 0) then
    begin
      Calculate;
      FStoredValue  := FCurrentValue;
      FEqualEntered := False;
      FAccumulated  := True;
    end
  else
    begin
      FStoredValue     := FCurrentValue;
      FAccumulated     := False;
      FCurrentValue    := 0;
      FCurrentValueStr := '0';
    end;

  FCurrentOperation := Value;
end;

procedure TCalculator.SetCurrentValue(const Value: Double);
begin
  FCurrentValue := Value;
end;

procedure TCalculator.SetCurrentValueStr(const Value: String);
begin
  FCurrentValueStr := Value;
end;

function TCalculator.DecimalPlaces(Number: String): String;
var
  NumberStr: string;
  DecimalPos: Integer;
  count: Integer;
  i: Integer;
  firsDecimal : String;
begin
  //inicializa a variável com vazio;
  firsDecimal := '';

  //remove o ponto do número
  NumberStr := StringReplace(Number,'.','',[rfReplaceAll]);

  // Encontra a posição do ponto decimal
  DecimalPos := Pos(FormatSettings.DecimalSeparator, NumberStr);

  // Se houver ponto decimal, conta os dígitos após ele
  // e encontra o primeiro digito decimal
  if DecimalPos > 0 then
  begin
    count := Length(NumberStr) - DecimalPos;
    firsDecimal := Copy(NumberStr, DecimalPos+1, 1);
  end
  else
    count := 0;

  //se não houver casas decimais, seta para 2
  if count = 0 then
    count := 2;

  //se as casas decimais forem maior que 16, limita a 16
  if count > 16 then
    count := 16;

  //retorna o carater # que servirá de máscara
  //para formatar o número, de acordo com as casas decimais
  for I := 1 to count do
  begin
    if firsDecimal = '0' then
      Result := Result + '0'
    else
      Result := Result + '#';
  end;
end;
  

Passo 3: Integração com o Formulário

No formulário principal (TfrmCalculator), vamos instanciar a classe TCalculator e conectar os eventos dos botões.

Delphi

type
  TfrmCalculator = class(TForm)
    lblResultado: TLabel;
    btnZero: TButton;
    btnUm: TButton;
    btnDois: TButton;
    btnTres: TButton;
    btnQuatro: TButton;
    btnCinco: TButton;
    btnSeis: TButton;
    btnOito: TButton;
    btnSete: TButton;
    btnNove: TButton;
    btnDecimal: TButton;
    btnPercentual: TButton;
    btnInverso: TButton;
    btnPotencia: TButton;
    btnRaizQuadrada: TButton;
    btnLimpar: TButton;
    btnDividir: TButton;
    btnMultiplicar: TButton;
    btnSubtrair: TButton;
    btnAdicionar: TButton;
    btnIgual: TButton;
    btnApagarUltimoDigito: TButton;
    btnLimparEntrada: TButton;
    btnInverterSinal: TButton;
    procedure btnLimparEntradaClick(Sender: TObject);
    procedure btnInverterSinalClick(Sender: TObject);
    procedure btnApagarUltimoDigitoClick(Sender: TObject);
    procedure btnDecimalClick(Sender: TObject);
    procedure btnLimparClick(Sender: TObject);
    procedure btnIgualClick(Sender: TObject);
    procedure btnNumeroClick(Sender: TObject);
    procedure btnOperacaoClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
    FUltimoDigitado : String;
    Calculator : TCalculator;
    procedure AtualizaResultado(const Key: String = '');
  public
    { Public declarations }
  end;

var
  frmCalculator: TfrmCalculator;

implementation

{$R *.dfm}

procedure TfrmCalculator.AtualizaResultado(const Key: String = '');
begin
  if Key <> '' then
    begin

      if Calculator.CurrentValueStr <> '0' then
        lblResultado.Caption := Calculator.CurrentValueStr + ' ' + Key
      else
        lblResultado.Caption := FUltimoDigitado + ' ' + Key;

    end
  else
    lblResultado.Caption := Calculator.CurrentValueStr;
end;

procedure TfrmCalculator.btnApagarUltimoDigitoClick(Sender: TObject);
begin
  Calculator.DeleteLastDigit;
  ActiveControl := nil; //Remove o foco dos botões
  AtualizaResultado;
end;

procedure TfrmCalculator.btnDecimalClick(Sender: TObject);
begin
  Calculator.AddDecimal;
  AtualizaResultado;
end;

procedure TfrmCalculator.btnIgualClick(Sender: TObject);
begin
  try
    Calculator.Calculate;
    AtualizaResultado;
  except
    on E: Exception do
      Application.MessageBox(PWideChar(E.Message), 'Erro', MB_OK+MB_ICONERROR);
  end;
end;

procedure TfrmCalculator.btnLimparClick(Sender: TObject);
begin
  Calculator.Clear;
  ActiveControl := nil;
  AtualizaResultado;
end;

procedure TfrmCalculator.btnLimparEntradaClick(Sender: TObject);
begin
  Calculator.CancelEntry;
  lblResultado.Caption := '0';
  ActiveControl := nil;
end;

procedure TfrmCalculator.btnNumeroClick(Sender: TObject);
begin
  Calculator.AddDigit((Sender as TButton).Caption);
  ActiveControl := nil;

  if Calculator.CurrentValueStr <> '0' then
    FUltimoDigitado := Calculator.CurrentValueStr
  else
    FUltimoDigitado := (Sender as TButton).Caption;

  AtualizaResultado;
end;

procedure TfrmCalculator.btnOperacaoClick(Sender: TObject);
begin
  try
    if (Sender as TButton).Name = 'btnMultiplicar' then
      Calculator.CalculateMultiplication
    else
    if (Sender as TButton).Name = 'btnDividir' then
      Calculator.CalculateDivision
    else
    if (Sender as TButton).Name = 'btnAdicionar' then
      Calculator.CalculateAddition
    else
    if (Sender as TButton).Name = 'btnSubtrair' then
      Calculator.CalculateSubtraction
    else
    if (Sender as TButton).Name = 'btnInverso' then
      begin
        Calculator.CalculateInverse;
        Calculator.Calculate;
      end
    else
    if (Sender as TButton).Name = 'btnPotencia' then
      begin
        Calculator.CalculatePower;
        Calculator.Calculate;
      end
    else
    if (Sender as TButton).Name = 'btnRaizQuadrada' then
      begin
        Calculator.CalculateSquareRoot;
        Calculator.Calculate;
      end
    else
    if (Sender as TButton).Name = 'btnInverterSinal' then
      Calculator.InvertSign
    else
    if (Sender as TButton).Name = 'btnPercentual' then
      Calculator.CalculatePercent;

    ActiveControl := nil;
    AtualizaResultado(Calculator.CurrentOperation);

  except
    on E: Exception do
     Application.MessageBox(PWideChar(E.Message), 'Erro', MB_OK + MB_ICONERROR);
  end;

end;

procedure TfrmCalculator.btnInverterSinalClick(Sender: TObject);
begin
  Calculator.InvertSign;
  ActiveControl := nil;
  AtualizaResultado;
end;

procedure TfrmCalculator.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Assigned(Calculator) then
    Calculator.Free;
end;

procedure TfrmCalculator.FormCreate(Sender: TObject);
begin
  Calculator := TCalculator.Create;
  KeyPreview := True; //Habilita o formulário para receber entradas pelo teclado
  AtualizaResultado;
end;

procedure TfrmCalculator.FormKeyPress(Sender: TObject; var Key: Char);
begin
  case Key of
    '0'..'9':
      begin
        Calculator.AddDigit(Key);

        if Calculator.CurrentValueStr <> '0' then
          FUltimoDigitado := Calculator.CurrentValueStr
        else
          FUltimoDigitado := Key;

        AtualizaResultado;
      end;
    '+', '-', '*', '/':
      begin
        Calculator.SetCurrentOperation(Key);
        AtualizaResultado(Key);
      end;
    '=':
      begin
        try
          Calculator.Calculate;
          AtualizaResultado;
        except
          on E: Exception do
            Application.MessageBox(PWideChar(E.Message), 'Erro', MB_OK+MB_ICONERROR);
        end;
      end;
    #8: // Backspace
      begin
        Calculator.DeleteLastDigit;
        AtualizaResultado;
      end;
    '.', ',':
      begin
        Calculator.AddDecimal;
        AtualizaResultado;
      end;
    #$1B: //Esc
      begin
         Calculator.Clear;
         AtualizaResultado;
      end;
    #13: // Enter
      begin
        try
          Calculator.Calculate;
          AtualizaResultado;
        except
          on E: Exception do
            Application.MessageBox(PWideChar(E.Message), 'Erro', MB_OK + MB_ICONERROR);
        end;
      end;
  end;
end;
  

Explicação do Código do Clone da Calculadora do Windows em Delphi

  1. Classe TCalculator:
    • Campos Privados: FCurrentValue, FCurrentValueStr e FStoredValue para armazenar os valores atuais e armazenados, FCurrentOperation para armazenar a operação atual, FDecimalEntered para controlar se o ponto decimal foi digitado, FEqualEntered para saber se o botão de igual foi acionado e FAccumulated para saber se está acumulando contas.
    • Construtor: Inicializa a calculadora chamando o método Clear.
    • Método Clear: Reseta os valores a operação e as variáveis de controle(FDecimalEntered, FEqualEntered , FAccumulated) .
    • Método AddDigit: Adiciona um dígito ao valor atual, tratando o caso inicial onde FCurrentValue é 0 ou FAccumulated é verdadeiro, e quando o botão de igual é acionado e não tem mais operações para fazer o método clear é chamado para resetar as variáveis.
    • Método SetOperation: Armazena o valor atual e define a operação atual, caso for uma conta acumulado efetua o cálculo.
    • Método Calculate: Realiza a operação armazenada entre os valores armazenados e atuais, e reseta a operação.
    • Método CalculateAddition: Armazena a operação de adição.
    • Método CalculateSubtraction: Armazena a operação de subtração.
    • Método CalculateMultiplication: Armazena a operação de multiplicação
    • Método CalculateDivision: Armazena a operação de divisão.
    • Método CalculatePercent: Armazena a operação de percentual.
    • Método CalculateSquareRoot: Armazena a operação de raiz quadrada.
    • Método CalculateInverse: Armazena a operação de inversão (1 dividido pelo número digitado).
    • Método CalculatePower: Armazena a operação de potência.
    • Método AddDecimal: Adiciona o separador de decimal.
    • Método DeleteLastDigit: Apaga o último dígito inserido.
    • Método InvertSign: Inverte o sinal do número digitado.
    • Método CancelEntry: Cancela a entrada;
    • Método SetCurrentValue: Seta o valor para a variável FCurrentValue e também é o método da propriedade CurrentValue.
    • Método SetCurrentValueStr: Seta o valor para a variável FCurrentValueStr e também é o método da propriedade CurrentValueStr.
    • Método SetCurrentOperation: Armazena a operação informada e também é o método da propriedade CurrentOperation.
    • Método DecimalPlaces: Resulta a máscara das casas decimais para formatar o valor que será exibido.
    • Método FormatNumberToFloat: Formata uma entrada em texto com máscara de número float, e resulta o texto formatado para ser exibido no rótulo.
    • Propriedades: CurrentValue, CurrentValueStr e CurrentOperation que podem ser acessadas quando a classe é instanciada.
  2. Formulário TfrmCalculator:
    • Eventos dos Botões: Cada botão de dígito chama o evento btnNumeroClick que adiciona o dígito à calculadora e atualiza o rótulo.
    • Operações: Botões de operação chamam o evento btnOperacaoClick que define a operação atual.
    • Igual: Botão de igual chama evento btnIgualClick que realiza a operação e exibe o resultado.
    • Limpar: Botão de limpar chama evento btnLimparClick que reseta a calculadora.
    • Cancelar Entrada: Botão de cancelar entrada chama o evento btnLimparEntradaClick que cancela a entrada atual.
    • Inverter Sinal: Botão de inverter sinal chama o evento btnInverterSinalClick que inverte o sinal do número digitado.
    • Apagar último dígito: Botão de apagar o último dígito chama o evento btnApagarUltimoDigitoClick que apaga o último dígito inserido.
    • Vírgula: Botão da vírgula chama o evento btnDecimalClick que adiciona a vírgula no número digitado para representar as casas decimais.
    • Criação do formulário: Na criação do formulário é chamado o evento FormCreate que cria o objeto Calculator, habilita o formulário para receber dados através do teclado e atualiza os dados no rótulo.
    • Fechamento do formulário: No fechamento do formulário é chamado o evento FormClose que limpa o objeto Calculator da memória, caso o mesmo esteja instanciado.
    • Tecla pressionada: Quando uma tecla numérica ou equivalente a calculadora é pressionada, é chamado o evento FormKeyPress, que captura a tecla pressionada e executa uma ação. Se for um dígito, ele aparecerá no rótulo, se for um sinal de operação, ela será armazenada para cálculo.

É importante consultar o código completo no link do github abaixo, para pegar todos os detalhes das propriedades dos componentes e formulário, como TabStop e Fontes utilizadas nos botões, e das bibliotecas declaradas na seção uses de cada unit.

Veja abaixo a ilustração do projeto Clone da Calculadora do Windows em Delphi:

Ilustração do projeto.

Código fonte do exemplo

Você pode fazer o download do exemplo do projeto através do repositório do github:

https://github.com/Gisele-de-Melo/CloneCalculator

Conclusão

Criar um clone da calculadora do Windows em Delphi é um ótimo exercício para aprender os conceitos básicos da programação orientada a objetos. Este projeto simples demonstra como estruturar uma aplicação utilizando classes e métodos, além de como manipular componentes da interface gráfica proporcionando uma experiência de usuário fluida e intuitiva. Com esta base, você pode expandir suas habilidades e explorar projetos mais complexos em Delphi.

Posts Relacionados



Nenhum comentário:

Postar um comentário

TParallel em Delphi: Paralelismo na Prática

Neste artigo, você aprenderá o que é paralelismo em Delphi, como funciona o TParallel, quando utilizá-lo, além de um exemplo prático comp...