Crea tu propia calculadora con Delphi (Parte I)

¡Buen camino amigo mío!

Creo que ya te comenté que íbamos a compartir una serie un tanto especial.  😉 Y lo prometido es deuda. Es mas, estos días pasados los he dedicado a otros afanes y deberes, con la sana intención de tener algo mas de tiempo libre para el blog, y que podamos iniciarla precisamente en este mes, también muy especial, como un gesto simbólico: un mes en el que se celebra el 20 aniversario de Delphi. Así que de alguna forma se enmarca dentro de ese espíritu del #DelphiWeek que todos estamos resaltando en las redes sociales.

La elección ya la tenía hecha y la había comentado previamente, tanto en el grupo de Facebook Delphi Solidario como en el blog, abriendo el año. Había elegido algo muy básico como pueda ser crear una calculadora, y de ahí el nombre de la serie, por supuesto: Crea tu propia calculadora con Delphi. La abordamos desde la ultima versión de nuestra herramienta XE7, pero eso no es motivo para que el código no pueda ser llevado a versiones anteriores, salvando las diferencias que puedan existir en detalles concretos del mismo (pongo por ejemplo de uno de esos detalles la forma en que se pueden invocar los métodos a través de su nombre mediante la RTTI, que sí existieron cambios, y que se utiliza en lo que será el ejemplo principal que compartiremos). Añado además, que la elección no es casual, -ahora que estamos tu y yo solos y nadie nos ve-, ya que fueron precisamente los primeros fragmentos de una calculadora los que me ayudaron a enamorarme, mas si cabe, de Delphi y de la programación. ¡Qué tontería, no!. Quizás por eso elegir este tema, en el marco de este año 2015, que por otros motivos es importante para mi. El detonante en mi caso, (de ese digamos «enamoramiento»), fue la lectura curiosa y atenta de una obra muy divertida de F. Charte, al menos a mi me lo  parecía, «Como programar con Delphi para Torpes»Delphi para torpes - Ed. Anaya publicado por la Editorial Anaya en el 98 y que me ayudó a divertirme al tiempo que me reafirmaba en esos primeros pasos que ya había dado un par de años antes pero que no había acabado de asimilar del todo. En aquel entonces yo vivía la era de Delphi 2, y el libro incluía entre otras cosas, cómo programar una calculadora básica.

Y ya que estamos en confianza, y me siento en paz conmigo mismo, he rescatado del baúl de correos que conservo, una de mis primeras cartas vinculadas a Delphi, que guardo como una pequeña joya entre los miles y miles de correos que todavía conservo. Está fechada el 29 de Noviembre de 1999 y se la dirigía al autor, en un arrebato de espontaneidad e ingenuidad, para agradecerle esas cosas que descubría en la programación que me hacia sentirme bien, y especial tras aquellas lecturas. Necesitaba compartir con alguien que había disfrutado con aquellos libros y no se me ocurrió nada mejor que escribirle estas líneas:

Estimado Sr.
 Me he atrevido a remitirle estas lineas de agradecimiento a su correo porque no me parecía 
oportuno hacerlo a traves de la dirección electrónica del foro. Me presentaré: Mi nombre es 
Salvador Jover y el motivo de estas lineas únicamente agradecerle la ayuda que me ha 
proporcionado la lectura de dos de sus libros: "Delphi para torpes" y las "Tecnicas avanzadas 
de Delphi 1 y 2 " de Anaya.
 Antes que nada, le comentaré que no me dedico a la programación, ni siquiera mi trabajo está 
relacionado con la misma. Soy un simple operario de una empresa, que desde hace algo más de 
tres años, en que me pude comprar mi primer ordenador, se ha acercado el mundo de la 
programación. Tengo 34 años. Mis conocimientos anteriormente eran escasos, algo de ms-dos 
y poco más. Me acerqué a Delphi a través del Curso IBM de Programación que lanzó con entornos 
gráficos: Delphi 1.0, Delphi 2.0, C++, etc... Me gustó Delphi y el deseo de ampliar información 
sobre el entorno me llevó a adquirir varios títulos. El libro de Rubenking "Dummies" me pareció 
excelente pero iba demasiado rápido para mí. Su libro de Técnicas avanzadas demasiado elevado. 
Su Delphi para torpes fue una bendición, pues me transmitió, al empezar a conectar con la 
programación, la idea de que no tenía que ser aburrida (el curso de ibm siempre partía del 
editor de textos...). 
Hoy, después de dos años casi, mi libro de almohada es el de Técnicas Avanzadas de Programación.
 Me parece un libro exquisito, bien estructurado y muy bien escrito. Permite una buena lectura y 
me sirve de referencía en multiples ocasiones. Un libro excelente... En fin, en pocas ocasiones 
he gastado tan bien un dinero. A pesar, de que en general, todos los títulos en Informática 
sean algo elevados para las personas con una economía modesta, no me puede doler el dinero que 
invertí en los dos.
 Y por supuesto... tan pronto como pueda actualizarme a un Delphi superior ( me estoy planteando 
comprar el 4 o el 5 en C/S) habrá un libro suyo mas encima de la mesa de mi Pc.
 Gracias y disculpe que le remita estas lineas a su correo particular.
 Atentamente,
 Salvador Jover

Han pasado 16 años de ese correo. La respuesta de Charte fue muy atenta y a la vuelta de pocos días me agradecía esas palabras con unas lineas breves de cortesía. Todavía recuerdo esa sensación de hormigueo al enviarlo y la sorpresa de recibirlo, mas cuando no era algo que esperara.

Cuando releía la carta, mientras me decidía sobre la oportunidad de incluirla en la entrada o no incluirla, no pude evitar emocionarme un poco, en la distancia de tener ahora 50 años y entonces solo 34. En aquel momento, ya llevaba unos dos años estudiando y leyendo sobre Delphi, en el poco -muy poco- tiempo que me dejaba mi trabajo: humilde como el de cualquier trabajador que pasaba, al pie de una máquina, largas jornadas de mas de 12 horas, para sacar adelante la familia. No, no me avergüenza decirlo. Estudiaba por las noches, sacando horas a la madrugada, hasta que me vencía el sueño.

Así que calculo por el contenido de esa carta, que daría mis primeros pasos en algún momento entre el año 96 y el 97, pudiendo conocer la primera versión de Delphi 1, con la que hice mi primer (y único) programa para un equipo con Windows 3.1. El resto Delphi 2, Delphi 5, Delphi 7, etc… hasta hoy. Unos 18 años…

Nunca había comentado estos detalles tan personales, pero el hecho de ser una fecha especial quizás lo merecía. Bueno… En realidad tampoco tienen demasiada importancia (lo son simplemente para mi). Eso sí, quizás de esta lectura, sí se pueda entender que exista al día de hoy un vinculo sentimental con una herramienta que cambió de alguna forma mi forma de entender el mundo y de comunicarme con él.

Una pregunta, ¿Me ayudarías a construir una calculadora…?

Yo no diría que sea mas popular que el típico «Hola Mundo» pero hasta donde me alcanza la memoria hemos estado acostumbrados a encontrarla en la Red, entre las preguntas de compañeros universitarios, que rastreaban como sabuesos los foros en búsqueda de completar ese ejercicio de clase donde se les pedía algo similar.

Quizás, a bote pronto, que sea una serie donde se vea construir una «calculadora» o algo que haga cálculos, parece que te puede dejar un poco frío… porque realmente puede ser algo trivial. O no. ¿Tú qué piensas?  Alguien me decía en una ocasión, en aquellos años en los que entrenaba y hacía deporte, que la dureza de un entrenamiento no estaba tanto en la cantidad de kilómetros que se corrían sino en cómo se habían corrido. Así pues, la idea de esta serie es poner sobre una hipotética mesa distintas perspectivas del mismo problema, cada cual encerrando distinta complejidad.

Comento esto porque he preparado para estas primeras entradas 3 demos, de las cuales hoy podríamos comentar las dos primeras, que son mas sencillas y que sirvan a quienes se acercan por primera vez a Delphi. La tercera demo, está parcialmente acabada y permite ver algunos detalles avanzados, dado que se utilizan conceptos mas «novedosos» o «recientes», clases genéricas, invocación de métodos mediante nombre y rtti, y por supuesto herencia. Además de interfaces, registros o estilos, para darle la misma apariencia en cualquiera de las cuatro plataformas. ¡Todo eso cabe también en una calculadora también!.

Cuando me puse a escribir el código de ese tercer ejemplo, buscaba otro enfoque distinto de los dos primeros, que son los que habitualmente encontramos en internet y que responden a un aplicación en la que el mismo formulario forma parte de la mecánica de la calculadora, tan íntimamente ligado que cualquier cambio en ella, supone reescribir y compilar la aplicación. La idea de ese tercer ejemplo va mas en la linea de crear un catalogo de operaciones, donde se liga cada operación con una clase, que sea la que realmente conozca cómo debe operar. Esto me permite por ejemplo separar lo que pueden ser esas operaciones básicas de las extendidas, y que sean cargadas estas últimas en tiempo de ejecución, disponibles para ser invocadas en el momento deseado, sin importar que tipo de ligadura le una a la interfaz de usuario.

Esta es una imagen de esa última versión, una vez aplicamos unos de los estilos.

captura

🙂

No vamos a anticipar… ¡Tiempo al tiempo!

¿Tú por donde empezarías…?

Te veo cara de incredulidad. Si te parece bien yo empezaría por una de las opciones mas sencillas para abordar nuestro problema. Dedicado a aquellos años felices donde el rock and roll era verdadero … Podríamos servirnos de las aplicaciones de tipo consola para Windows, de forma que nos olvidemos de los detalles del interfaz. La ventaja es que nos permite jugar con un par de métodos para escribir y leer en la consola, y con un poco de imaginación, hacemos un menú casero para jugar a calcular.

¿Quereis disfrutar del interfaz?  🙂

Seguro que hacia tiempo que no lo veías

conso2


procedure Menu;
begin
  Cabecera;
  WriteLn('øEscoja quÈ desea hacer?:');
  WriteLn('(+)para sumar');
  WriteLn('(-)para restar:');
  WriteLn('(*)para multiplicar:');
  WriteLn('(/)para dividir:');
  WriteLn('(S)para salir:');
  WriteLn('-------------------------');
  Readln(Operador);
  ClearConsoleScreen;
end;

En las aplicaciones de tipo consola, el código forma parte del archivo de proyecto, de nuestro dpr. Este módulo contiene  inicialmente el mínimo imprescindible que podría ser reducido simplemente a un begin end, aun a costa de no hacer nada. Nosotros (**) le hemos añadido un bucle repeat … until que le permitirá al usuario operar hasta que pulse la opción (S) Salir.  Muestra cada linea de texto y espera a que el usuario se decida. Y hecho esto limpia el texto para volver a escribir al inicio.

(*) La función ClearConsoleScreen, o con nombre similar, se puede localizar con distintas variantes en Internet y todas muy también similares. Obtendrá el handle de la ventana para acceder a un buffer que le permite limpiar el contenido y volver a fijar la posición del cursor.

(**) Ni idea de porque me gusta hablar en plural para referirme a lo que hago. Se siente uno mas acompañado.  🙂


begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    repeat
      Menu;
      if IntentarSalida(Operador) then
        Continue;
      PrimerOperando;
      SegundoOperando;
      CalcularResultado;
    until Uppercase(Operador) = 'S';
  except
    on E: Exception do
      WriteLn(E.ClassName, ': ', E.Message);
  end;
end.

Básicamente lo que hace es: esperar que el usuario elija que operación desea y en función de esa decisión seguir adelante o evaluar si vuelve al menu o sale definitivamente. Si sigue adelante, pedirle cual va a ser el primer operando, pedirle que introduzca el segundo operando y finalmente calcular el resultado, que se mostraría en pantalla hasta que el usuario pulsa cualquier tecla para volver al menu principal.

Permitidme que solo nos fijemos únicamente en el procedimiento del calculo, puesto que el resto es jugar con los métodos ReadLn y WriteLn, el primero para que el usuario introduzca un valor de entrada y el segundo como salida hacia nuestra consola, como hace al invocar el procedimiento PrimerOperando:


procedure PrimerOperando;
begin
  Cabecera;
  WriteLn('Escribe el primer operando...');
  WriteLn;
  WriteLn;
  Readln(Operando1);
  ClearConsoleScreen;
end;

El método CalcularResultado hace una llamada a DoCalcularResultado, que realmente no necesita los parámetros de entrada porque ya estaban en las variables y eran visibles al procedimiento (por pereza se quedó así).


function DoCalcularResultado(AOperador: Char; AOperando1, AOperando2: Double): Double;
begin
  case AOperador of
    '+': Result := AOperando1 + AOperando2;
    '-': Result := AOperando1 - AOperando2;
    '*': Result := AOperando1 * AOperando2;
    '/': Result := AOperando1 / AOperando2;
  else
    Result := 0;
  end;
end;

Solo quiero que observes un detalle que puede pasar inadvertido: Si esta función representa el corazón del cálculo, fuera de esas cuatro operaciones básicas ya implementadas, nos obligaría a reescribir nuevos casos en nuestra estructura case. Es una desventaja expuesto de esa forma ¿no te parece?. Además no controlamos excepciones propias del dominio del problema, como pueda ser la división por cero, o que el parámetro de entrada no pueda convertirse a un numero decimal.

Código fuente: CalculadoraCON.dpr

Por cierto, no me gustaría cerrar el tema sin comentar la oportunidad de estas aplicaciones en modo consola, para aquellos momentos en los que estáis jugando con el lenguaje y hprog_delphi_2006aciendo pequeños test para evaluar estructuras nuevas, por aquello de la simplicidad de su formato. Me viene a la mente uno de los libros que mas me sorprendió por esa razón. El publicado por Ivan Hladni, «Programación Delphi 2006» donde el autor se vale del modelo consola para explicar las estructuras del lenguaje básicas de object pascal y C++ en la primera parte de su libro. Me sorprendió muy gratamente por la claridad con la que el lector recibía toda esa información.

De vuelta a lo tradicional…

El estudiante que buscaba esa calculadora quizás le pareciera poca cosa, por eso de que esperaba que el interfaz tuviera botones y fuera algo mas moderno, por lo que me veo casi obligado moralmente a presentar el segundo ejemplo mas en la linea de lo que podemos encontrar en la red. 🙂 Enfocaremos ahora el tema desde la perspectiva de una aplicación windows, y para ello podemos iniciar un nuevo proyecto VCL. ¿Hay algo mas tradicional que eso?

Siendo justos, lo que vamos a encontrar en internet quizás no nos guste demasiado. Es posible que la calculadora te acabe funcionando pero los entresijos, se convierten en lineas y lineas de código no demasiado legibles, ligadas a los mismos componentes que forman parte de la interfaz, del tipo: if button1.tag = … then if button2.tag = … else if … else if…  Podéis perder unos minutos y hacer una búsqueda con los términos «calculadora delphi». 327.000 resultados. Algunos videos. Realmente son todos muy parecidos y todos adolecen de lo mismo. No digo que no se hayan subido a la red con buena fe pero en mi opinion algunos nos muestran la cara mas fea de Delphi.

Así que harías bien si te detuvieras un momento antes de que el primer impulso fuera el de añadir los botones y empezar a escribir código a lo loco. Nuestro segundo ejemplo, basado en aquella PATOCALC del libro para torpes, nos ayudará a entender lo que quiero compartir contigo, respecto a la importancia de que busques la claridad en tu código. La abstracción como tal, es algo que ya hemos conocido desde la perspectiva de casos de uso en UML, dicho esto con las reservas adecuadas. Prácticamente, toda la operativa interna de esta calculadora básica se apoya en cinco métodos y tres variables (FlagNumero, Operando y Operador):

    procedure ProcesarOperacion(AOperador: Char);
    procedure ProcesarDigito(AOperando: Char);
    procedure DesplazarDigitoALaIzquierda(AOperando: Char);
    procedure IntroduceDigito(AOperando: Char);
    procedure ActualizaDisplay(const AOperando: String);

Así que, aunque nuestro código sigue adoleciendo la íntima ligadura con el interfaz del usuario, porque no se han ligado estos métodos a una clase independiente del interfaz pero ha ganado en claridad y es algo que creo que acabaras agradeciendo. Estos métodos se corresponden con operaciones que somos capaces de abstraer de la calculadora. Por ejemplo, ProcesarDigito es una abstracción de la entrada de un nuevo dígito numérico mientras que ProcesarOperacion representa las distintas operaciones básicas. Si tienes que elegir entre:

DesplazarDigitoALaIzquierda(AOperando: Char)

y su pírrica implementación efectiva,

lbDisplay.Caption:= lbDisplay.Caption + AOperando;

no me queda duda que la primera no va a dejarte indiferente si se pierde en el interior de un método con 50 lineas muy similares, mientras que la segunda posiblemente te pase inadvertida.

Pasa a la parte práctica…

Abre un nuevo proyecto VCL. Todo el código va a incluirse en la de las unidad principal del proyecto, cuyo interfaz es similar a este formulario. Todos los componentes que puedes ver son botones (TButton), salvo el componente TLabel que nos sirve de display.

calculadora_tipica

Guarda tanto el proyecto como la unidad que se ha creado, con el nombre que desees. Yo guardé el (.pas) con el nombre UCalculadora. El siguiente paso es poner un nombre decente a tu formulario. Para ello, busca la propiedad Name en el inspector de objetos y dale el nombre que te parezca adecuado. En el ejemplo: frmCalculadoraTrad (por aquello de calculadora tradicional). Y el siguiente paso, puede ser crear el interfaz, y tomarte tu tiempo dando nombres a esos botones para que sea mas fácil visualizar en que puntos de tu código aparecen (en el caso de que aparezcan).

 Al final de este proceso, tendrás un formulario que todavía no contiene una sola linea de implementación (es decir, que no hace nada si es ejecutado). Es hora de hacer funcional el interfaz.

Para que te sea mas fácil seguir los siguientes comentarios, vamos a darle un vistazo al código final de forma que tengas a la vista el total de líneas que incluye. Puedes también optar si lo deseas, por eliminar la unidad añadida por defecto en el nuevo proyecto y sustituirla por ésta, de la cual enlazo también dentro de una carpeta con el (.pas y el .dfm).


unit UCalculadora;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, 
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
  TfrmCalculadoraTrad = class(TForm)
    lbDisplay: TLabel;
    btn7: TButton;
    btn8: TButton;
    btn9: TButton;
    btn4: TButton;
    btn5: TButton;
    btn6: TButton;
    btn1: TButton;
    btn2: TButton;
    btn3: TButton;
    btn0: TButton;
    btn00: TButton;
    btnPuntoDecimal: TButton;
    btnIgual: TButton;
    btnDividir: TButton;
    btnMultiplicar: TButton;
    btnRestar: TButton;
    btnSumar: TButton;
    btnAC: TButton;
    procedure btnClick(Sender: TObject);
    procedure btnPuntoDecimalClick(Sender: TObject);
    procedure btnOperarClick(Sender: TObject);
    procedure btnIgualClick(Sender: TObject);
    procedure btnACClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btn00Click(Sender: TObject);
  private
    { Private declarations }
    FlagNumero: Boolean;
    Operando: Double;
    Operador: Char;
    procedure ProcesarOperacion(AOperador: Char);
    procedure ProcesarDigito(AOperando: Char);
    procedure DesplazarDigitoALaIzquierda(AOperando: Char);
    procedure IntroduceDigito(AOperando: Char);
    procedure ActualizaDisplay(const AOperando: String);
  public
    { Public declarations }
  end;
var
  frmCalculadoraTrad: TfrmCalculadoraTrad;
implementation
{$R *.dfm}

procedure TfrmCalculadoraTrad.btn00Click(Sender: TObject);
begin
  ProcesarDigito('0');
  ProcesarDigito('0');
end;
procedure TfrmCalculadoraTrad.btnACClick(Sender: TObject);
begin
   if lbDisplay.Caption = '0' then
    begin
      Operando:= 0;
      Operador:= ' ';
    end
   else
      lbDisplay.Caption:= '0';
   FlagNumero:= False;
end;

procedure TfrmCalculadoraTrad.btnClick(Sender: TObject);
begin
  ProcesarDigito((Sender as TButton).Caption[1]);
end;

procedure TfrmCalculadoraTrad.btnIgualClick(Sender: TObject);
begin
  ProcesarOperacion(Operador);
  Operador:= ' ';
  Operando:= 0;
end;

procedure TfrmCalculadoraTrad.btnOperarClick(Sender: TObject);
begin
  ProcesarOperacion((Sender as TButton).Caption[1]);
end;

procedure TfrmCalculadoraTrad.btnPuntoDecimalClick(Sender: TObject);
begin
   if FlagNumero then
    begin
       if StrScan(Pchar(lbDisplay.Caption), ',') = nil then
         DesplazarDigitoALaIzquierda(',');
    end
   else
    begin
     DesplazarDigitoALaIzquierda('0');
     IntroduceDigito(',');
    end;
end;

procedure TfrmCalculadoraTrad.DesplazarDigitoALaIzquierda(AOperando: Char);
begin
  lbDisplay.Caption:= lbDisplay.Caption + AOperando;
end;

procedure TfrmCalculadoraTrad.FormCreate(Sender: TObject);
begin
   Operando:= 0;
   Operador:= ' ';
   FlagNumero:= False;
end;

procedure TfrmCalculadoraTrad.IntroduceDigito(AOperando: Char);
begin
  lbDisplay.Caption:= AOperando;
  FlagNumero:= True;
end;

procedure TfrmCalculadoraTrad.ActualizaDisplay(const AOperando: String);
begin
  lbDisplay.Caption:= AOperando;
end;

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;

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;
end.

Pulsa aquí para descargar UCalculadora.

Entender lo que hace…

Todos hemos tenido que usar en alguna ocasión una calculadora. ¿Tienes una a mano…?  Hemos hecho cálculos con ella y conocemos mas o menos cómo funciona. Sabemos que el usuario va a poder pulsar dígitos numéricos (0..9) o bien que formen parte del número, como la coma decimal (.) o el signo negativo (-), y que, cada nuevo dígito procesado, hace que se desplace el parcial introducido hacia la izquierda, dejando un espacio para alojar la pulsación entrante. Nuestra calculadora puede establecer además una cantidad de dígitos máxima, a discreción nuestra, a la hora de su diseño. Hemos ligado este valor final a la variable Operando de tipo Double.

Por otro lado, a ese primer operando le sigue la operación, representada en la variable Operador, que nosotros hemos definido por el tipo Char y que almacenará la representación textual que representa la operación deseada.  Dado que una gran mayoría de operaciones admitirán dos operandos sobre los que operar, nos encontraremos en la necesidad de conocer cuando el usuario va a escribir un nuevo operando y cuando se encuentra a mitad de esa introducción del valor. Realmente no necesitamos dos operandos sino que nos basta acumular el resultado de la operación en uno. Para conocer cuando estamos frente a un nuevo operando o cuando nos encontramos a mitad de establecerlo nos ayuda la variable FlagNumero, de tipo booleano. Para el primer caso, su valor false, indicaría que debemos vaciar el display y prepararnos para recibir un dígito numérico. Para el segundo caso, un valor true, nos permitiría desplazar el parcial del operando introducido a la izquierda, según lo comentado en el párrafo anterior. Nos queda simplemente, averiguar cuando debemos establecer un valor u otro:

Este interruptor, FlagNumero, valdrá false al crearse la calculadora y cada vez que se procese un operador aritmético o de resultado. El primer dígito numérico introducido hará que el interruptor cambie a true para que el comportamiento sea el adecuado y se produzcan los sucesivos desplazamientos a la izquierda.

Esto lo podeis ver claramente en los fragmentos ProcesarDigito() y Procesar Operacion().

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;

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;
procedure TfrmCalculadoraTrad.IntroduceDigito(AOperando: Char);
begin
  lbDisplay.Caption:= AOperando;
  FlagNumero:= True;
end;

Una vez visualizas estos conceptos, es fácil comprender la implementación (si es el caso que nunca hubieras construido una calculadora).

Es posible que la pulsación de un botón necesite una respuesta particular al evento que sucede al pulsarlo, como puede ser en nuestro caso el punto decimal, que necesita ademas discriminar si ya se ha introducido en el parcial del operador. En otros casos, podremos agrupar en un único manejador de evento, determinados grupos que mantienen por su naturaleza un respuesta común. El caso de los dígitos numéricos (0..9) o las operaciones básicas que hemos considerado (‘+’,’-‘,’x’,’/’). Lo tienes en:

procedure TfrmCalculadoraTrad.btnClick(Sender: TObject);
begin
  ProcesarDigito((Sender as TButton).Caption[1]);
end;
procedure TfrmCalculadoraTrad.btnOperarClick(Sender: TObject);
begin
  ProcesarOperacion((Sender as TButton).Caption[1]);
end;

El parametro Sender en el evento OnClick del boton, representa el origen de la pulsación. A través de esta referencia (gracias a la jerarquía que establece la herencia), podemos mediante un simple casting, conocer el componente que ha desencadenado el evento.

Ejecutadlo. Poned unos puntos de parada. Añadid alguna operación mas. Esta calculadora básica ya te permitirá jugar.

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.

Hacia la segunda parte de esta serie y el tercer ejemplo…

Hemos visto dos ejemplos muy básicos de calculadora, con las mínimas operaciones. El segundo de ellos ya es funcional, en el sentido de que se acerca mas a la idea intuitiva de calculadora que establecimos al inicio de la entrada.

capturaSin embargo, realmente no hemos pensado en términos de clases. Puedes llegar un poco mas lejos. ¿Quieres compartir ese viaje conmigo?

Te anticipo una pequeña parte de ese tercer ejemplo, que vamos a abordar en las siguientes partes de la serie.

También tiene su interfaz. Pero hay una diferencia importante, y es que hemos abstraído el concepto de calculadora, como una clase que va a colaborar con nuestro interfaz. No va a formar parte de el.

Este es el código que te refiero


unit UICalculadoraFMX;

interface

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

type
  TfmUICalculadora = 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;
    bnDigitoSigno: TButton;
    bnMultiplicacion: TButton;
    bnRC: TButton;
    bnResta: TButton;
    bnCE: TButton;
    bnSuma: TButton;
    bnAC: TButton;
    lbDisplay: TLabel;
    lbError: TLabel;
    StyleBook1: TStyleBook;
    procedure FormCreate(Sender: TObject);
    procedure bnDobleCeroClick(Sender: TObject);
    procedure bnDigitoSignoClick(Sender: TObject);
    procedure TeclaGenericaClick(Sender: TObject);
    procedure bnACClick(Sender: TObject);
  private
    { Private declarations }
    FCalculadora: TCalculadoraBasica;
    procedure TeclaRaizCuadradaClick(Sender: TObject);
    procedure VisualizarEstadoErrorOperacion;
  public
    { Public declarations }
  end;

var
  fmUICalculadora: TfmUICalculadora;

implementation

{$R *.fmx}
{$R *.Macintosh.fmx _MACOS}
{$R *.Windows.fmx MSWINDOWS}
{$R *.iPhone.fmx IOS}
{$R *.NmXhdpiPh.fmx ANDROID}
{$R *.LgXhdpiPh.fmx ANDROID}

uses UCBExtendTypes;


{ TfmUICalculadora }

procedure TfmUICalculadora.bnACClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesaDigito((Sender as TButton).Text);
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.bnDigitoSignoClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesaDigito('+-');
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.bnDobleCeroClick(Sender: TObject);
begin
                   FCalculadora.ProcesaDigito('0');
  lbDisplay.Text:= FCalculadora.ProcesaDigito('0');
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.TeclaGenericaClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesaDigito((Sender as TButton).Text);
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.FormCreate(Sender: TObject);
begin
  FCalculadora:= TCalculadoraBasica.Create(self);
  //Registramos la operacion raiz cuadrada
  //que extiende la calculadora básica
  FCalculadora.RegisterEtiqueta(EtiquetaRaizCuadrada);
  bnRC.OnClick:= TeclaRaizCuadradaClick;
  bnRC.Text:= EtiquetaRaizCuadrada.Representacion;
  lbDisplay.Text:= FCalculadora.LeeDisplay;
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.TeclaRaizCuadradaClick(Sender: TObject);
begin
  lbDisplay.Text:= FCalculadora.ProcesaDigito(EtiquetaRaizCuadrada.Lexema);
  VisualizarEstadoErrorOperacion;
end;

procedure TfmUICalculadora.VisualizarEstadoErrorOperacion;
begin
  lbError.Visible:= FCalculadora.ExisteError;
end;

end.

La linea

  FCalculadora:= TCalculadoraBasica.Create(self);

en el evento de creación del formulario, creará la instancia de TCalculadoraBasica, la clase que representa la calculadora, y ésta se relacionará con el interfaz mediante los métodos públicos que creamos necesarios.

Nuestro proximo destino, será una calculadora que se pueda ejecutar en distintas plataformas, por lo que optaremos por FMX (Firemonkey) en lugar de VCL para construirla.

Es una gran semana. No te olvides. Estamos celebrando el 20 aniversario de Delphi: #DelphiWeek.

4 comentarios sobre “Crea tu propia calculadora con Delphi (Parte I)

Agrega el tuyo

  1. Excelente!!! Yo hace tiempo pense lo mismo, que era muy absurdo seguir con el «Hola Mundo» e intente hacer mi calculadora basica (claro en aplicacion de escritorio), pero la unica diferencia es que yo coloque un TEdit fuera del formulario con el focus siempre en el para capturar el valor ascii de la tecla presionada y asi tanto manejarla con el mouse como con el teclado!!! (si existe otra forma me agradaria la propuesta!!!)

    Me gusta

  2. Hola:
    Gracias por el comentario y por supuesto, me alegra te que guste el artículo.

    En realidad creo que un enfoque real del problema podría ser: ¿Quién tiene la responsabilidad de capturar las pulsaciones del teclado y transformarlas en acciones de la calculadora?

    Me parece importante contestar a esa cuestión ya que va a depender según la respuesta que pueda ser alguna entidad externa a la calculadora o por el contrario, ella misma.

    Digo esto, porque los últimos capítulos de esta serie buscaban un enfoque donde la calculadora pudiera cargar dinámicamente funcionalidades nuevas mediante un sistema de plugins. Era un planteamiento mas ambicioso. En ese caso, de haber respondido el formulario externo, ¿como podría capturar algo que no conoce en el tiempo de diseño? Entonces se necesitaría solventar nuevos problemas que figuradamente se pondrían sobre el tapete.

    Otra pregunta que se me ocurre, ¿podría el usuario sobrescribir los atajos predeterminados y crear los suyos propios?

    Quiero decir con esto que posiblemente surgirían preguntas adicionales si de verdad intentaremos profundizar. De hecho a lo largo de los distintos capítulos, se presentan distintos enfoques que van llegando un poco mas lejos sin que ello quiera decir que sea incorrecto uno u otro.

    Como tu has comentado, añadir un control de edición como display puede ser una forma de solventar el problema considerando que el interfaz de usuario sea responsable de esta tarea. Otros compañeros podrían haber optado por asociar acciones a los botones y utilizar los atajos de teclado asociados a cada acción. Otra via: el contenedor. Siendo un formulario, existiría un evento OnKeyPreview que podría dar la oportunidad de capturar los eventos del teclado.

    Cada decisión tiene sus pros y sus contras y al final todo va a depender del desarrollador de la aplicación, en esa decisión de diseño, donde valora hasta donde va la llegar, que va a ganar y perder al fijar una postura frente al problema.

    Saludos

    Me gusta

Deja una respuesta

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. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Blog de WordPress.com.

Subir ↑

Marina Casado

Escritora y Doctora en Literatura Española. Periodista cultural. Madrid, España

Sigo aqui

Mi rincon del cuadrilatero

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

https://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi, C++ Builder and related technologies...

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphic Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general

A %d blogueros les gusta esto: