Crea tu propia calculadora con Delphi (Apéndice Parte IV)

Buen camino.

Retomo nuevamente la idea expresada al final de la parte cuarta de la serie, en este apéndice. Si te parece, hago memoria sobre el último comentario que servirá de punto de partida:

 En esta entrada nos hemos valido de métodos y notificaciones (eventos), que nos permiten actualizar esa interfaz en respuesta de las sucesivas peticiones. Sin embargo, existe la posibilidad de enfocar estos cambios a través de la manipulación de interfaces: Imagina que nuestro display, en nuestro ejemplo representado por un string que almacena el valor a visualizar, fuera sustituido por un tipo interfaz, IDisplayView, que suponemos sea capaz de responder a manipular esta información. Cualquier objeto (instancia) que implementara dicha interfaz y que fuera conectado a la interfaz, aportaría al modelo la capacidad de que TCalculadoraBasica actuara sobre él sin conocer el tipo, en base al acuerdo marco de los métodos que el interfaz establece…

Esta inquietud viene a poner sobre la figurada mesa de discusión, la necesidad de que exista no solo una vía de comunicación, que ya establecimos entre la IU (interfaz de usuario) y la clase que representa el controlador (el componente calculadora) sino un canal abierto en dirección contraria, que repercuta los cambios que ocasiona la primera comunicación. En nuestro caso, la invocación de cualquier método de la calculadora, pongamos por caso, el proceso de un digito cualquiera, repercutiría en los adecuados cambios en el display del usuario, fruto precisamente de esa actuación.

Hice trampa…  😉

¿Te diste cuenta…?

Al convertir, pongamos por caso, el método ProcesarDigito( ) en una función que retornaba un string,

function TCalculadoraBasica.ProcesarDigito(const ALexema: Char): String;

 que representaba el valor de ese resultado, abría una ventana a que el interfaz pudiera acceder al valor del display (el resultado de esa operación).

Es un pecado venial.  😀

Sin ese retorno, difícilmente hubiera podido actualizar el IU en las primeras partes de la serie. Lo cual no es incorrecto pero nos obliga a reintroducir la invocación cada vez que es aplicada una acción, de forma que se mantenga coherente el interfaz respecto al estado interno del Display en el componente.

Evolucionar o no evolucionar…

Sin duda: ¡evolucionar!.

El siguiente paso, en esa comunicación es entender que podemos crear un modelo de notificaciones basadas en eventos. Recuerda como limpiábamos la ventana ante un error, apoyándonos en dos eventos antagónicos que se disparaban respectivamente para comunicar que había entrado en estado interno de error o que salía de el. La interpretación de esta comunicación es algo así como si la interfaz le dijera a la clase control, “Déjame saber cuando emerge el error  y cuando es limpiado y no tendré que comprobar que éste existe cada vez que proceso una acción”.

Te muestro, un fragmento del evento de creación del formulario IU, que denota esta alternativa vía de comunicación:


...
 FCalculadora:= TCalculadoraBasica.Create(Self, FDisplay);
   with FCalculadora do
   begin
     RegisterEtiqueta(EtiquetaRaizCuadrada);
     OnNotifyErrorEvent:= VisualizarEstadoErrorOperacion;
     OnCleanNotifyErrorEvent:= VisualizarEstadoErrorOperacion;
   end;
...

 Así que en ese sentido, si quisiéramos usar esta vía para mantener sincronizado el Display, sería algo factible creando un evento que notificase los cambios y dejar la oportunidad de que el IU se suscriba. Luego tendríamos que buscar cada punto entre bastidores de nuestro componente para canalizar la invocación del evento cada vez que fuera necesario.

Pero… quizás haya otra forma de hacer las cosas.

Hablar el mismo idioma es lo natural

Bueno. Ahora tenemos que introducir el concepto de interfaz, con el que quizás ya te hayas familiarizado. En términos familiares ¿Qué representa una interfaz? La respuesta para mí al menos es: un contrato, un acuerdo. Cuando declaramos una interfaz, expresamos la idea de que cualquier bicho viviente que la posea, se dotará de sus poderes, con independencia de que sea un ser esmirriado y debilucho.

Imagina la interfaz:


ISuperHeroe = Interfaz
  Procedure Volar;
end;

Podríamos declarar la clase THeroe con ese nuevo superpoder, estableciendo en su declaración de tipo esta nueva habilidad (e implementando como hace efectivo su super poder de vuelo).


Type
  TPersonaSuperSuper = class(TPersona, ISuperHeroe)
    ...
  end;

En nuestra calculadora vamos a aplicar este concepto que nos va a permitir no solo crear un canal de comunicación sino que este se integre de una forma natural en nuestro componente, sin referencias al objeto real que esconde el interfaz declarado.

Te muestro el nuevo esquema que representa las relaciones que van a establecerse:

nuevo_diagrama

Receta que vamos a cocinar

El primer ingrediente que necesitamos es definir propiamente la interfaz. Una posible sería:


   IDisplay = Interface
    procedure SetTextDisplay(const Value: String);
    function GetTextDisplay: String;
   End;

 Ahora tendríamos que hacer los cambios en nuestro componente, de forma que modificamos el tipo string por el nuevo tipo IDisplay, en el registro que representa la pantalla de la calculadora.


   TDisplay = record
    Display: IDisplay;
    function ExistePuntoDecimal(const APuntoDecimal: Char): Boolean;
  end;

Hecho esto, nos resta hacer las sustituciones , ante el quejido lastimero del compilador que detecta un tipo no esperado. Los errores son lógicos. Donde ahora hay declarado un tipo interfaz antes existía un string.

Pues, pongamos en tarea y hagamos los cambios.

Donde antes decía por ejemplo

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

ahora  podemos poner

procedure TCalculadoraBasica.DesplazarDigitoALaIzquierda(AOperando: Char);
begin
  FDisplay.Display.SetTextDisplay(FDisplay.Display.GetTextDisplay + AOperando);
end;
(Nota del 22/02/2015: Las prisas por acabar el apéndice, me llevaron a que el fragmento anterior, se modificara posteriormente y que el codigo se actualizase).

Si te fijas bien, estamos diciendo lo mismo pero el matiz es que delegamos en el objeto que implementa la interfaz la responsabilidad de las acciones. Al componente calculadora le da igual quien se esconda tras la interfaz. Ese es verdaderamente el punto fuerte y lo que marca la diferencia.

¡Importante!

Ya no necesitamos que los métodos públicos de la clase como ProcesaDigito, sean funciones que retornen un string. Porque van a actualizar el valor de la pantalla a través de la referencia a IDisplay. Así que tanto ProcesaDigito( ) como LeeDisplay( ) ahora pueden convertirse en procedimientos, pues carece de sentido que sigan siendo funciones.

Vale la pena que os muestre la nueva unidad UCalculadora, una vez hechos los cambios.


unit UDBCalculadora;

interface

uses System.SysUtils, System.Variants, System.Classes, Generics.Collections,
  UDBClasses;

const
  MaxDigitos = 12;
type
   TDisplayInternalState = (csOk, csError);

   IDisplay = Interface
    procedure SetTextDisplay(const Value: String);
    function GetTextDisplay: String;
   End;

   TDisplay = record
    strDisplay: String;
    Display: IDisplay;
    function ExistePuntoDecimal(const APuntoDecimal: Char): Boolean;
  end;

  TCalculadoraBasica = class(TComponent)
  private
    FDisplay: TDisplay;
    FlagNumero: Boolean;
    Operando: Double;
    Operador: String;
    DIS: TDisplayInternalState;
    FCatalogo: TObjectDictionary<String, TDigito>;
    FOnCleanNotifyErrorEvent: TNotifyEvent;
    FOnNotifyErrorEvent: TNotifyEvent;
    FErrorMessage: String;
    FDecimalSeparator: Char;
    FThousandSeparator: Char;
    FDecimalCount: Integer;
    procedure DesplazarDigitoALaIzquierda(AOperando: Char);
    procedure IntroduceDigito(AOperando: Char);
    function EsOperadorInmediato(const ALexema: String): Boolean;
    function GetOperadorText: String;
    procedure SetOnCleanNotifyErrorEvent(const Value: TNotifyEvent);
    procedure SetOnNotifyErrorEvent(const Value: TNotifyEvent);
    procedure SetErrorMessage(const Value: String);
    function GetErrorMessage: String;
    function GetDecimalSeparator: Char;
    function GetThousandSeparator: Char;
    procedure SetDecimalCount(const Value: Integer);
  protected
    procedure ActualizaDisplay(const AOperando: String); virtual;
    procedure DoReset; virtual;
    procedure RegisterCatalogo(ACatalogo: Array of TEtiqueta); virtual;
  public
    constructor Create(AOwner: TComponent; AIDisplay: IDisplay);
    destructor Destroy; override;
    procedure ExceptionToError(E: Exception);
    function ExisteError: Boolean;
    procedure LeeDisplay;
    procedure ProcesarDigito(const ALexema: Char);
    procedure ProcesarOperacion(const ALexema: String);
    procedure RegisterEtiqueta(AEtiqueta: TEtiqueta);
    function Reset: String;
    property DecimalSeparador: Char read GetDecimalSeparator;
    property ThousandSeparator: Char read GetThousandSeparator;
    property DecimalCount: Integer read FDecimalCount write SetDecimalCount;
    property ErrorMessage: String read GetErrorMessage;
    property OperadorText: String read GetOperadorText;
    property OnNotifyErrorEvent: TNotifyEvent read FOnNotifyErrorEvent write SetOnNotifyErrorEvent;
    property OnCleanNotifyErrorEvent: TNotifyEvent read FOnCleanNotifyErrorEvent write SetOnCleanNotifyErrorEvent;
  end;

implementation

uses Rtti;

{ TCalculadoraBasica }

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

constructor TCalculadoraBasica.Create(AOwner: TComponent; AIDisplay: IDisplay);
var
  FS: TFormatSettings;
begin
  FDisplay.Display:= AIDisplay;
  FCatalogo:= TObjectDictionary<String, TDigito>.Create;
  RegisterCatalogo(CatalogoSimbolosBasicos);
  Operando:= 0;
  Operador:= ' ';
  FlagNumero:= False;
  DIS:= csOK;
  FErrorMessage:= '';
  FS:= TFormatSettings.Create;
  FDecimalSeparator:= FS.DecimalSeparator;
  FThousandSeparator:= FS.ThousandSeparator;
  FDecimalCount:= 4;
  FDisplay.strDisplay:= '0';
end;

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

destructor TCalculadoraBasica.Destroy;
var
  KeyLexema: String;
begin
  for KeyLexema in FCatalogo.Keys do FCatalogo.Items[KeyLexema].Free;
  FCatalogo.Clear;
  FreeAndNil(FCatalogo);
  inherited Destroy;
end;

function TCalculadoraBasica.EsOperadorInmediato(const ALexema: String): Boolean;
begin
  if (ALexema = ' ') or (ALexema = '=') then
    Result:= False
  else Result:= TDigitoOperador(FCatalogo.Items[ALexema]).EsOperadorInmediato;
end;

procedure TCalculadoraBasica.ExceptionToError(E: Exception);
begin
  if Assigned(E) then
  begin
   DIS:= csError;
   FErrorMessage:= E.Message;
   if Assigned(FOnNotifyErrorEvent) then FOnNotifyErrorEvent(Self);
  end;
end;

function TCalculadoraBasica.ExisteError: Boolean;
begin
  Result:= (DIS = csError);
end;

function TCalculadoraBasica.GetDecimalSeparator: Char;
begin
  Result:= FDecimalSeparator;
end;

function TCalculadoraBasica.GetErrorMessage: String;
begin
  Result:= FErrorMessage;
end;

function TCalculadoraBasica.GetOperadorText: String;
begin
  if (Operador = ' ') or (Operador = '=') then
    Result:= Operador
  else Result:= TDigitoOperador(FCatalogo.Items[Operador]).GetRepresentacion;
end;

function TCalculadoraBasica.GetThousandSeparator: Char;
begin
  Result:= FThousandSeparator;
end;

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

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

procedure TCalculadoraBasica.LeeDisplay;
var
  FS: TFormatSettings;
begin
  FS:= TFormatSettings.Create;
  FDisplay.Display.SetTextDisplay(Format('%'+Format('%d.%d', [MaxDigitos-FDecimalCount-1,FDecimalCount])+'f', [StrToFloat(FDisplay.strDisplay)], FS));
end;

procedure TCalculadoraBasica.ProcesarDigito(const ALexema: Char);
begin
   if ExisteError then Exit;

   if FlagNumero then
    begin
      if Length(FDisplay.Display.GetTextDisplay) < MaxDigitos then
      begin
         if (FDisplay.strDisplay = '0') and (ALexema = '0') then
           Exit
         else
          begin
            //evaluamos si ya exite el punto decimal
            if (ALexema = DecimalSeparador) and
               (FDisplay.ExistePuntoDecimal(DecimalSeparador)) then
               Exit;

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

   LeeDisplay;
end;

procedure TCalculadoraBasica.ProcesarOperacion(const ALexema: String);
begin
   if ExisteError then Exit;

   if EsOperadorInmediato(ALexema) then Operador:= ALexema;

   if ((Operador = ' ') or (Operador = '=')) then
     Operando:= StrToFloat(FDisplay.strDisplay)
   else
     try
       Operando:= TDigitoOperador(FCatalogo.Items[Operador]).ExecuteAction(Self, Operando, StrToFloat(FDisplay.strDisplay));
     except
       on E: Exception do if not ExisteError then ExceptionToError(E);
     end;
   ActualizaDisplay(FloatToStr(Operando));
   FlagNumero:= False;
   Operador:= ALexema;

   LeeDisplay;
end;

procedure TCalculadoraBasica.RegisterCatalogo(ACatalogo: array of TEtiqueta);
var
  i: Integer;
begin
  for i:= Low(ACatalogo) to High(ACatalogo) do
    RegisterEtiqueta(ACatalogo[i]);
end;


procedure TCalculadoraBasica.RegisterEtiqueta(AEtiqueta: TEtiqueta);
var
  FClass: TDigitoClass;
begin
  FClass:= AEtiqueta.Clase;
  FCatalogo.Add(AEtiqueta.Lexema, FClass.Create(AEtiqueta, self));
end;

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

procedure TCalculadoraBasica.SetDecimalCount(const Value: Integer);
begin
  if (Value <> FDecimalCount) then
  begin
    if Value > 0 then
      FDecimalCount := Value
    else FDecimalCount:= 0;
  end;
end;

procedure TCalculadoraBasica.SetErrorMessage(const Value: String);
begin
  FErrorMessage := Value;
end;

procedure TCalculadoraBasica.SetOnCleanNotifyErrorEvent(
  const Value: TNotifyEvent);
begin
  FOnCleanNotifyErrorEvent := Value;
end;

procedure TCalculadoraBasica.SetOnNotifyErrorEvent(const Value: TNotifyEvent);
begin
  FOnNotifyErrorEvent := Value;
end;

procedure TCalculadoraBasica.DoReset;
begin
   if FDisplay.strDisplay = '0' then
    begin
      Operando:= 0;
      Operador:= ' ';
    end
   else
      FDisplay.strDisplay:= '0';
   FlagNumero:= False;
   DIS:= csOK;
   FErrorMessage:= '';
   if Assigned(FOnCleanNotifyErrorEvent) then FOnCleanNotifyErrorEvent(Self);
end;

{ TDisplay }

function TDisplay.ExistePuntoDecimal(const APuntoDecimal: Char): Boolean;
begin
  Result:= (StrScan(Pchar(StrDisplay), APuntoDecimal) <> nil);
end;



end.



El postre de la receta

Para que todos los cambios efectuados puedan verse en la práctica, debemos ahora actuar sobre la interfaz de usuario. Necesitamos un componente que nos pueda servir de pantalla y para ello, vamos a definir un nuevo TLabel.

type
   TNewLabel = class(TLabel, IDisplay)
    procedure SetTextDisplay(const Value: String);
    function GetTextDisplay: String;
   end;

El nuevo componente sabe comportarse de acuerdo a la interfaz IDisplay.

{ TNewLabel }

function TNewLabel.GetTextDisplay: String;
begin
  Result:= self.Text;
end;

procedure TNewLabel.SetTextDisplay(const Value: String);
begin
   Text:= Value;
end;

¿No te parece algo sencillo?

Como no lo tenemos creado en tiempo de diseño, ya que no lo hemos instalado en el entorno, simplemente lo crearemos en el evento de construcción del formulario, y se lo facilitaremos a nuestra calculadora (yo he utilizado el constructor para ello).

A partir de ese momento, volveremos a disponer de un display funcional con la diferencia de que podríamos haberle entregado cualquier otro componente, que soportara la interfaz y el resultado sería igualmente funcional.

Este es el código modificado:


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
   TNewLabel = class(TLabel, IDisplay)
    procedure SetTextDisplay(const Value: String);
    function GetTextDisplay: String;
   end;

  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;
    btnAC: TButton;
    bnRC: TButton;
    lbError: TLabel;
    lbErrorMessage: TLabel;
    lbOperador: TLabel;
    TrBDecimalCount: TTrackBar;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    procedure bnDobleCeroClick(Sender: TObject);
    procedure btnACClick(Sender: TObject);
    procedure btnClick(Sender: TObject);
    procedure bnOperarClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure bnPuntoDecimalClick(Sender: TObject);
    procedure TrBDecimalCountChange(Sender: TObject);
  private
    { Private declarations }
    FCalculadora: TCalculadoraBasica;
    FDisplay: TNewLabel;
    procedure TeclaRaizCuadradaClick(Sender: TObject);
    procedure VisualizarEstadoErrorOperacion(Sender: TObject);
  public
    { Public declarations }
  end;

var
  frmCalculadoraTrad: TfrmCalculadoraTrad;

implementation

{$R *.fmx}

uses UCBExtendTypes;

procedure TfrmCalculadoraTrad.btnClick(Sender: TObject);
begin
  with FCalculadora do
  begin
    ProcesarDigito((Sender as TButton).Text.Chars[0]);
    if not ExisteError then lbOperador.Text:= '';
  end;
end;

procedure TfrmCalculadoraTrad.FormCreate(Sender: TObject);
begin
   FDisplay:= TNewLabel.Create(Self);
   FDisplay.Parent:= Self;
   FDisplay.Position.X:= 32;
   FDisplay.Position.Y:= 0;
   FDisplay.Text:= '0';
   FDisplay.Width:= 225;
   FDisplay.TextSettings.HorzAlign:= TTextAlign(2);
   FDisplay.TextSettings.FontColor:=  TAlphaColorRec.Blue;


   FCalculadora:= TCalculadoraBasica.Create(Self, FDisplay);
   with FCalculadora do
   begin
     RegisterEtiqueta(EtiquetaRaizCuadrada);
     OnNotifyErrorEvent:= VisualizarEstadoErrorOperacion;
     OnCleanNotifyErrorEvent:= VisualizarEstadoErrorOperacion;
   end;
   VisualizarEstadoErrorOperacion(FCalculadora);
   bnRC.OnClick:= TeclaRaizCuadradaClick;
   bnRC.Text:= EtiquetaRaizCuadrada.Representacion;
   FCalculadora.LeeDisplay;
end;

procedure TfrmCalculadoraTrad.TeclaRaizCuadradaClick(Sender: TObject);
begin
  with FCalculadora do
  begin
    ProcesarOperacion(EtiquetaRaizCuadrada.Lexema);
    lbOperador.Text:= OperadorText;
  end;
end;

procedure TfrmCalculadoraTrad.TrBDecimalCountChange(Sender: TObject);
begin
   with FCalculadora do
   begin
     if not ExisteError then
     begin
       DecimalCount:= Trunc(TrBDecimalCount.Value);
       LeeDisplay;
     end;
   end;
end;

procedure TfrmCalculadoraTrad.VisualizarEstadoErrorOperacion(Sender: TObject);
begin
  with FCalculadora do
  begin
    lbError.Visible:= ExisteError;
    lbErrorMessage.Text:= ErrorMessage;
  end;
end;

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

procedure TfrmCalculadoraTrad.bnPuntoDecimalClick(Sender: TObject);
begin
  with FCalculadora do
  begin
    ProcesarDigito(DecimalSeparador);
    lbOperador.Text:= OperadorText;
  end;
end;

procedure TfrmCalculadoraTrad.bnDobleCeroClick(Sender: TObject);
begin
  with FCalculadora do
  begin
     ProcesarDigito('0');
     ProcesarDigito('0');
     if not ExisteError then lbOperador.Text:= '';
  end;
end;

procedure TfrmCalculadoraTrad.btnACClick(Sender: TObject);
begin
  FCalculadora.Reset;
  lbOperador.Text:= '';
  FCalculadora.DecimalCount:= Trunc(TrBDecimalCount.Value);
  FCalculadora.LeeDisplay;
end;



{ TNewLabel }

function TNewLabel.GetTextDisplay: String;
begin
  Result:= self.Text;
end;

procedure TNewLabel.SetTextDisplay(const Value: String);
begin
   Text:= Value;
end;


end.

 Reflexión

Me gustaría que volviera ahora al articulo de Daniele Teti que citábamos en la anterior entrada. Quizás ahora veas mayor paralelismo y encuentres sentido, si es que su lectura te pareció difícil y extraña. Espero que te pueda haber ayudado este apéndice.

Descarga el código fuente

Código

Nos leemos en la quinta y última parte de la serie. Espero que la disfruteis.

 

Un comentario sobre “Crea tu propia calculadora con Delphi (Apéndice Parte IV)

Agrega el tuyo

  1. Hoy he hecho una modificación en el código fuente que se acompaña ya que al incluir las rutinas de formato de decimales generaban un error que se ha corregido en el nuevo código.
    Se ha reemplazado el código original del articulo por el nuevo para no inducir a errores.

    Basicamente el problema ha surgido ya que no puedes considerar la vista formateada util en el esquema de razonamientos sino que la debes reservarla exclusivamente cuando se ejecuta el procedimiento que leerá el valor del display.

    Te pongo un ejemplo de porque no funcionaba bien:

    Al intentar escribir por ejemplo ’99’ suponiendo que la calculadora tuviera activos 4 decimales, había propuesto:

    procedure TCalculadoraBasica.DesplazarDigitoALaIzquierda(AOperando: Char);
    begin
    FDisplay.Display.SetTextDisplay(FDisplay.Display.GetTextDisplay + AOperando);
    end;

    La función hubiera establecido un valor correcto para el primer dígito 9, pero para el segundo hubiera escrito en el display ‘9,0001’. en lugar de ‘99,0000’.

    Las prisas nunca son buenas. Internamente había guardado ‘9,0000’ + ‘9’ y por redondeo mostraba ‘9,0001’. 🙂

    Así que he vuelto a revisar el código para que opere correctamente, tal y como lo hacia en los ejemplos anteriores.

    Corregido.

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

Blog de WordPress.com.

Subir ↑

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

http://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi 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 Delphi Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

/*Prog*/ Delphi-Neftalí /*finProg*/

Blog sobre programación de Neftalí -Germán Estévez-

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: