Menu

Tradução

Português English Español Français

Clone da Calculadora do Windows em Delphi

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

Recursividade em Delphi: Conceitos, Exemplos e Aplicações Práticas

Neste post, você aprenderá quando usar recursividade em Delphi, suas vantagens e desvantagens, além de um exemplo prático implementa...