Buen camino

Está finalizando este segundo día de nuestro #DelphiWeek especial, y espero que te hayas acercado a ver si todavía podemos compartir una lineas.

Hoy daremos un pequeño paso mas.

Decía en uno de los párrafos finales de la entrada anterior, refiriéndome de forma amplia a los dos primeros ejemplos que compartimos:

Eso sí… considerad que sigue existiendo el mismo problema que en la primera: seguimos necesitando modificar y compilar nuevamente nuestro código cada vez que deseamos ampliar el numero de operaciones disponibles. Además, todavía es poco reutilizable, dado que debemos arrastrar necesariamente todo nuestro interfaz.

Crea tu propia calculadora con Delphi (Parte I)

Aludía a dos aspectos que, en mi humilde opinión, se podían destacar. Uno de ellos el mas evidente, que era que el interfaz fuera íntima parte de la calculadora. Cualquier intento de reutilizar el código, te hubiera obligado a planteártelo. Otro menos evidente a primera vista, es que aunque puedes añadir cuantas operaciones quieras, siempre te vas a ver obligado a compilar nuevamente. ¿Recuerdas la estructura case que discriminaba la operación en función del parámetro que recibía?

Se veía tal que así:

procedure TfrmCalculadoraTrad.ProcesarOperacion(AOperador: Char);
begin
   case Operador of
     ' ' : Operando:= StrToFloat(lbDisplay.Caption);
     '+' : Operando:= Operando + StrToFloat(lbDisplay.Caption);
     '-' : Operando:= Operando - StrToFloat(lbDisplay.Caption);
     'x' : Operando:= Operando * StrToFloat(lbDisplay.Caption);
     '/' : Operando:= Operando / StrToFloat(lbDisplay.Caption);
   end;
   ActualizaDisplay(FloatToStr(Operando));
   FlagNumero:= False;
   Operador:= AOperador;
end;

Hoy vamos a dar uno de esos pasos y mañana, miércoles, mas o menos a esta hora, compartiremos otro avance, respecto al segundo punto.

Seguimos en el ejemplo demo 2 que habíamos compartido. Pero si te parece, dado que nuestro propósito es compilar en todas las plataformas disponibles por nuestra herramienta, vamos a crear un nuevo proyecto multiplataforma. Pulsamos sobre el menú principal, File, New, Multi Device Application Delphi. Podemos elegir la aplicación vacía (Blank Application) entre las distintas opciones, que es la que mas nos puede interesar en este momento. Una vez hecho esto, sobre el formulario principal, podemos volver a poner los distintos bótones existentes en la demo 2, tal que así:

formulario_fmx

De momento, seguimos centrados en la plataforma Win32, que por defecto se activará al crear tu proyecto, simplemente por simplicidad, por evitar tener que lidiar con otros problemas que puedan surgir, no relacionados con el mismo código: es posible que intentes hacer el deploy para las plataformas móviles y existan impedimentos que puedan ser “normales” (no importaste los sdks adecuados, no has ejecutado las aplicaron PasServer para comunicar el entorno con la maquina que hace de anfitrión y sobre la que se ejecutará la aplicación, etc…). Es decir, que pueden haber muchos motivos por los que en este punto, y como consejo, puede ser adecuado avanzar en el marco de win32, aun cuando ya hayamos elegido la plataforma de FireMonkey. En cualquier momento podremos activarlas.

El paso de hoy es muy sencillo. Veámoslo:

Tras crear el nuevo proyecto, que habrá añadido el formulario que te sirve de interfaz, y sobre el que has añadido los distintos componentes, añadimos una nueva unidad, que podemos llamar UCalculadora.pas. Para añadirla, otra vez sobre el menú principal, File, New, Unit-Delphi.

Sobre esta nueva unidad, crearemos una clase, con la sana intención de desligar la calculadora propiamente del interfaz de usuario (¿recuerdas al final de la primera parte, el código que te comentaba respecto a la demo3?). Ahora estamos dando un paso sobre la demo 2 para acercarnos a esta idea.

Esta es la clase TCalculadoraBasica, que vamos a especificar e implementar.

type
  TCalculadoraBasica = class(TComponent)
  private
    FDisplay: String;
    FlagNumero: Boolean;
    Operando: Double;
    Operador: Char;
    procedure DesplazarDigitoALaIzquierda(AOperando: Char);
    procedure IntroduceDigito(AOperando: Char);
  protected
    procedure DoReset; virtual;
    procedure ActualizaDisplay(const AOperando: String); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    function ExisteError: Boolean;
    function LeeDisplay: String;
    function ProcesarDigito(const ALexema: Char): String;
    function ProcesarOperacion(const ALexema: Char): String;
    function Reset: String;
  end;

 Nuestra calculadora ahora arrastra algunos de los métodos y variables que existían en el interfaz de usuario anteriormente. Los remarco en color rojo para que los distingas mejor. Si te fijas, si anteriormente pertenecían al ámbito privado del formulario ahora algunos de ellos deberán ser visibles y por lo tanto, los moveremos a la parte publica de la clase. Es el caso de los métodos ProcesarDigito() y ProcesarOperacion(). 

Añadimos algunas funciones nuevas, fruto de la nueva naturaleza de la calculadora. La sustitución del componente label por un simple string (FDisplay), nos permite anticipar que necesitaremos un método de lectura, por lo que añadimos LeeDisplay. La acción del Reset, sería el equivalente a nuestro anterior evento al pulsar AC.

De momento, no vamos a evaluar los posibles errores, pero la existencia de esta función nos va a servir en futuras partes de la serie.

Veamos la implementación, que es prácticamente similar a la existente en la demo 2.

implementation

{ TCalculadoraBasica }

procedure TCalculadoraBasica.ActualizaDisplay(const AOperando: String);
begin
  FDisplay:= AOperando;
end;

constructor TCalculadoraBasica.Create(AOwner: TComponent);
begin
  inherited;
  Operando:= 0;
  Operador:= ' ';
  FlagNumero:= False;
end;

procedure TCalculadoraBasica.DesplazarDigitoALaIzquierda(AOperando: Char);
begin
  FDisplay:= FDisplay + AOperando;
end;

function TCalculadoraBasica.ExisteError: Boolean;
begin
  Result:= False;
end;

procedure TCalculadoraBasica.IntroduceDigito(AOperando: Char);
begin
  if AOperando = ',' then
    FDisplay:= '0' + AOperando
  else
    FDisplay:= AOperando;

  if FDisplay <> '0' then FlagNumero:= True;
end;

function TCalculadoraBasica.LeeDisplay: String;
begin
  Result:= FDisplay
end;

function TCalculadoraBasica.ProcesarDigito(const ALexema: Char): String;
begin
   if FlagNumero then
    begin
      if Length(FDisplay) < MaxDigitos then
      begin
         if (FDisplay = '0') and (ALexema = '0') then
           Exit(LeeDisplay)
         else
          begin
            //evaluamos si ya exite el punto decimal
            if (ALexema = ',') and (StrScan(Pchar(FDisplay), ',') <> nil) then
              Exit(LeeDisplay);

            DesplazarDigitoALaIzquierda(ALexema);
          end;
      end;
    end
   else
    IntroduceDigito(ALexema);

   Result:= LeeDisplay;
end;

function TCalculadoraBasica.ProcesarOperacion(const ALexema: Char): String;
begin
   case Operador of
     ' ','=' : Operando:= StrToFloat(FDisplay);
     '+' : Operando:= Operando + StrToFloat(FDisplay);
     '-' : Operando:= Operando - StrToFloat(FDisplay);
     'x' : Operando:= Operando * StrToFloat(FDisplay);
     '/' : Operando:= Operando / StrToFloat(FDisplay);
   end;
   ActualizaDisplay(FloatToStr(Operando));
   FlagNumero:= False;
   Operador:= ALexema;

   Result:= LeeDisplay;
end;

function TCalculadoraBasica.Reset: String;
begin
  DoReset;
  Result:= LeeDisplay;
end;

procedure TCalculadoraBasica.DoReset;
begin
   if FDisplay = '0' then
    begin
      Operando:= 0;
      Operador:= ' ';
    end
   else
      FDisplay:= '0';
   FlagNumero:= False;
end;

end.

Hemos tenido que modificar ligeramente los métodos principales que se van a comunicar con el interfaz, respectivamente ProcesarOperación y ProcesarDigito.

En el primero, la linea

     ' ' : Operando:= StrToFloat(lbDisplay.Caption);

ahora va a evaluar el dígito ‘=’,

     ' ','=' : Operando:= StrToFloat(FDisplay);

de forma que se refunde el evento de click inicial para esta pulsación.

Algo similar nos ha sucedido con el evento correspondiente al punto decimal, que fue refundido también en el código nuevo, correspondiente a la función ProcesarDigito()

Antes:

procedure TfrmCalculadoraTrad.ProcesarDigito(AOperando: Char);
begin
   if FlagNumero then
    begin
      if Length(lbDisplay.Caption) < 12 then
      begin
         if (lbDisplay.Caption = '0') and (AOperando = '0') then
           Exit
         else
           DesplazarDigitoALaIzquierda(AOperando);
      end;
    end
   else
    IntroduceDigito(AOperando);
end;

Ahora:

function TCalculadoraBasica.ProcesarDigito(const ALexema: Char): String;
begin
   if FlagNumero then
    begin
      if Length(FDisplay) < MaxDigitos then
      begin
         if (FDisplay = '0') and (ALexema = '0') then
           Exit(LeeDisplay)
         else
          begin
            //evaluamos si ya exite el punto decimal
            if (ALexema = ',') and (StrScan(Pchar(FDisplay), ',') <> nil) then
              Exit(LeeDisplay);

            DesplazarDigitoALaIzquierda(ALexema);
          end;
      end;
    end
   else
    IntroduceDigito(ALexema);

   Result:= LeeDisplay;
end;

Hemos cambiado la naturaleza de ambos métodos, pasando de ser procedimientos a convertirse en funciones que van a devolver una cadena con el resultado final del Display, de forma que el Interfaz de usuario, podrá actualizar el label adecuado.

Pero… ahora habrás dado un pequeño paso para desligar ambos. Si lo piensas bien: ¿Qué mas te da que sea un label, un botón, un panel, un item de un StringList, quien te devuelva el valor que muestra la pantalla de nuestra calculadora? Quiero resaltar al decir esto, que esta unidad, que representa tu calculadora, es susceptible de ser reutilizada en el ámbito de otra aplicación que la pueda necesitar y eso te sitúa en una posición mejor que en la entrada anterior.

Por lo tanto, nuestro interfaz de usuario, la unidad que yo he llamado UMain.pas, se reducirá sensiblemente, quedando disponible la misma funcionalidad.

Júzgalo tu mismo:


unit UMain;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  UCalculadora;

type
  TfrmCalculadoraTrad = class(TForm)
    bnSiete: TButton;
    bnOcho: TButton;
    bnNueve: TButton;
    bnCuatro: TButton;
    bnCinco: TButton;
    bnSeis: TButton;
    bnUno: TButton;
    bnDos: TButton;
    bnTres: TButton;
    bnCero: TButton;
    bnDobleCero: TButton;
    bnPuntoDecimal: TButton;
    bnResultado: TButton;
    bnDivision: TButton;
    bnMultiplicacion: TButton;
    bnResta: TButton;
    bnSuma: TButton;
    lbDisplay: TLabel;
    btnAC: TButton;
    procedure bnDobleCeroClick(Sender: TObject);
    procedure btnACClick(Sender: TObject);
    procedure btnClick(Sender: TObject);
    procedure bnOperarClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure bnPuntoDecimalClick(Sender: TObject);
  private
    { Private declarations }
    FCalculadora: TCalculadoraBasica;
  public
    { Public declarations }
  end;

var
  frmCalculadoraTrad: TfrmCalculadoraTrad;

implementation
{$R *.fmx}

procedure TfrmCalculadoraTrad.btnClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesarDigito((Sender as TButton).Text.Chars[0]);
end;

procedure TfrmCalculadoraTrad.FormCreate(Sender: TObject);
begin
   FCalculadora:= TCalculadoraBasica.Create(Self);
end;

procedure TfrmCalculadoraTrad.bnOperarClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesarOperacion((Sender as TButton).Text.Chars[0]);
end;

procedure TfrmCalculadoraTrad.bnPuntoDecimalClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesarDigito(',');
end;

procedure TfrmCalculadoraTrad.bnDobleCeroClick(Sender: TObject);
begin
                   FCalculadora.ProcesarDigito('0');
  lbDisplay.Text:= FCalculadora.ProcesarDigito('0');
end;

procedure TfrmCalculadoraTrad.btnACClick(Sender: TObject);
begin
  FCalculadora.Reset;
  lbDisplay.Text:= '0';
end;
end.

Estas son las unidades que hemos visto en este segundo día: Código fuente.

Y mañana que…

No te olvides de acercarte a ultima hora del día. Viviremos la tercera jornada de esta semana especial.

Daremos el siguiente paso sobre el mismo escenario para dar los primeros avances respecto al segundo aspecto citado al iniciar esta segunda parte, y que hacia referencia a la necesidad de compilar sucesivamente la aplicación, a medida que se nos plantean nuevas operaciones. Nos apoyaremos en la herencia y crearemos un catalogo de operaciones disponibles, abordando esta tarea desde la perspectiva de una colección genérica, como puede ser la clase:

TObjectDictionary<String, TDigitoOperador>, parametrizada por un string y una clase base de las distintas operaciones.

Un comentario sobre “Crea tu propia calculadora con Delphi (Parte II)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s