El Rinconcito
de Delphi

De Clipper a Delphi


En su día elaboraba y publicaba en otro sitio, el Club Delphi, entre otras preguntas y respuestas una selección sencilla de las que podían venir bien a los que procedían del lenguaje Clipper, un estándard entonces, y se introducian en lenguales visuales, concretamente Delphi. Espero que sigan siendo actuales.

¿Qué puedo utilizar en Delphi cuando en Clipper usaba las funciones...?

Array(). La creación de arrays se realiza definiendo la variable como tal

TablaX:array[0..20] of string;

indicando el número de elementos y el tipo de estos. Además, en Delphi se pueden crear listas, tipo TList o TStringList.

AT(). Para conocer la posición de una expresión en una cadena de caracteres se utiliza

Pos(Substr; string)

y devuelve un entero, que será 0 si no está contenida.

BOF (). Marca de inicio de fichero. No varía su sintaxis, pero en Delphi no es una función.

Tabla.bof;

CHR(). Devuelve el valor numérico, según la tabla ASCII, de un caracter. Puede utilizarse la misma función. No obstante, para el uso que normalmente se ha dado en Clipper utilizaríamos la forma #número_de_tecla

'cadena'+chr(13) => 'cadena'+#13

DATE(). Devuelve la fecha del sistema. No hay variación.

DTOC(). Convierte una fecha en una cadena de caracteres.

DatetoStr(fecha);

DOW(). Devuelve un entero indicando el día de la semana .

DayOfWeek(fecha);

EOF(). Marca de final de fichero. La misma sintaxis, pero en Delphi no es una función.

Tabla.eof;

INKEY(). No hay una forma similar en Delphi de detener la ejecución de un programa. Tiene poco sentido en general dado la diferencia de filosofía entre los dos lenguajes. No obstante se puede realizar mediante instrucciones Timer de Windows.

INT(). Devuelve el valor entero de una variable numérica. Funcionan de la misma forma.

LASTKEY(). La obtención de las teclas pulsadas se realiza en Delphi mediante los eventos. No tiene sentido el uso de funciones de este tipo.

LEN(). Devuelve el número de dígitos de una cadena de caracteres o variable alganumérica. En Delphi se utiliza la función length.

Length(variable_string);

LOG(). En Delphi hay dos funciones de logaritmos, Log2 y Log10. Estas funciones matemáticas se encuentran en la unit Math.

LOWER(). Para convertir una cadena a minúsculas puede utilizarse LowerCase, que afecta a caracteres de 7 bits, o AnsiLowerCase para los 8 bits.

AnsiLowerCase(variable_string);

LTRIM(). Para elimiar los caracteres blancos a la izquierda en una cadena se utiliza TrimLeft.

MOD(). Para obtener el resto de una operación se utiliza también Mod de la siguiente forma

i mod j => Devuelve el resto en la division.
i div j => Devuelve la parte entera.

STR(). Teniendo en cuenta que el tipo de variables es distinto en los dos lenguajes, para convertir una o variable numérica en una expresión alfanumérica, en Delphi se utiliza InttoStr y FloattoStr. La precisión en decimales se puede realizar mediante la instrucción Format.

variable_string:=InttoStr(variable_integer);
variable_string:=FloattoStr(variable_real);

SUBSTR(). La obtención de una parte de una cadena se consigue con la función Copy y similar tratamiento.

Copy(cadena,posición_inicio,número_de_digitos);

TRIM(). Para eliminar los caracteres blancos de una cadena se utiliza la misma función Trim.

VAL(). Con las mismas observaciones que en la función STR(), para convertir una cadena o variable alfanumérica en una expresión numérica se utiliza StrtoInt y StrtoFloat.

variable_integer:=StrtoInt(cadena);
variable_real:=StrtoInt(cadena);

FUNCIONES DE FECHAS EN GENERAL. Se obtiene el día, mes y año de una variable tipo fecha con el procedimiento DecodeDate.

DecodeDate(Fecha,Anyo,Mes,Dia);

FUNCIONES SOBRE BASES DE DATOS. La amplitud de Delphi en cuanto a tipos de Bases de Datos a utilizar, las múltiples formas de acceder y las diferencias de filosofía en la programación dirigida a objetos, impide establecer relaciones entre los dos lenguajes.

FUNCIONES ACHOICE() Y DBEDIT(). En Delphi hay diversidad de componentes para realizar estas funciones de tipo browse. Los mas similares podrían ser StringGrid y DBGrid respectivamente, pero su funcionamiento sigue siendo distinto por completo.

FUNCIONES UTILIZADAS EN LAS CLASES DEFINIDAS EN CLIPPER 5.X. Habida cuenta que Clipper no es un lenguaje que soporte la programación orientada a objetos como tal, al contrario de lo que ocurre con Delphi, no hay similitud en cuanto a funciones ni técnica de programación en este aspecto.


¿Qué puedo utilizar en Delphi cuando en Clipper usaba los comandos...?

@..BOX, SAY, GET, TO. Los comandos de este tipo dejan de tener sentido. Las coordenadas en Delphi son propiedades comunes a los objetos y es a través de éstos como se introducen datos, se dibujan marcos o se muestra un texto.

ACCEPT. Los datos se introducen por medio de objetos, como se especifica en el punto anterior. El que hace las veces de este comando o del Get/Read puede ser el TEdit.

BEGIN SEQUENCE. El tratamiento de errores, para este comando en concreto, es similar a la utilización de excepciones, que es el método utilizado en Delphi y que se comenta en la pregunta dedicada al fichero Errorsys.

CANCEL. Para concluir la ejecución de un programa se utiliza Close, siempre que se produzca en la ficha principal, por ej. Form1.Close, o con Application.Terminate.

CLEAR. Dado que el funcionamiento en Delphi es por ventanas (formularios) y no por pantalla completa, no tiene sentido este comando. No obstante, aunque la propiedad Clear es común a algunos objetos, no guarda relación alguna.

ListBox1.Clear;

DO. Aunque en Clipper 5 era un comando obsoleto, se sustituía por las llamadas a funciones del tipo mifuncion(). En Delphi es similar a esta última forma, siempre que se indique el lugar (form) en que se encuentra y éste se localice dentro de la claúsula Uses.

Si la función o el procedimiento se encuentran en el mismo fichero desde el que se les llama, basta con

mifuncion(parámetros);

Si se localiza en un lugar distinto hay que hacer referencia a éste

Uses Unit2;
...
form2.mifuncion(parámetros);

FUNCTION, PROCEDURE. Las funciones y procedimientos han de estar definidas antes de su ejecución. Si pertenecen a un form han de incluirse dentro de la definición de éste.

type
TForm1 = class(TForm)
procedure MiProcedimiento(Sender: TObject);

KEYBOARD. Aunque el funcionamiento es distinto, se puede enviar un caracter manejando los eventos del componente que se está utilizando.

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
key := #72;
end;

En este ejemplo, y utilizando el evento KeyPress del componente TEdit, sea cual sea la tecla pulsada la convertimos a la 72.

MENU TO. En lugar de este comando para generar menus, se puede sustituir por TMainMenu, TPopupMenu, etc.

PARAMETERS. El paso de parámetros se utiliza de forma similar a las versiones de Clipper 5.x. La diferencia fundamental estriba en que al definir el procedimiento o función se le debe especificar el tipo de parámetros que va a recibir.

procedure MiProcedimiento(MiVariable:integer);
...
MiProcedimiento(12);

RENAME. La sintaxis para cambiar de nombre un fichero es

RenameFile('c:\..\fichero_antiguo','c:\..\fichero_nuevo');

RETURN. Un procedimiento o función está comprendidos entre Begin y end; no obstante se utiliza Return de forma similar a Clipper para devolver el resultado de la función. Si se quiere abortar el proceso antes de su finalización se puede hacer con Exit.

RUN. Para ejecutar un programa externo se utiliza la función ExecuteFile.

STORE. Para asignar valores a variables es necesario declararlas con anterioridad

var
Ivar:integer;
Svar1,Svar2:String;

e inicializarlas una vez dentro del begin correspondiente.Una variable no inicializada produce una advertencia en compilación, si no está declarada se produce un error.


Diferencias en condicionales.

Condicional IF

Estructura simple en Clipper:

If <expresion>
  <instrucciones>
EndIf

y su equivalencia en Delphi (Pascal):

If <expresion> then <instrucción>; // Para una sola instrucción
If <expresion> then begin // Para varias instrucciones
  <instrucciones>
End;

Estructura anidada en Clipper:

If <expresion>
  <instrucciones>
Else // o ElseIf
  If <expresion>
    <instruccion>
  Else
    <instruccion>
  EndIf
EndIf


Estructura anidada en Delphi:

if <expresion> then begin
  <instruccciones>
end else if <instruccion> then <instruccion>;

Instrucción Case


Estructura genérica en Clipper:

Do Case
  Case <condicion>
    <intrucciones
  Case <condicion>
    <instrucciones>
  Other
    <instrucciones
EndCase

La primera diferencia es que, mientras la condición Case en Clipper puede ser de cualquier tipo, en Delphi
ha de ser ordinal.
Case <expresion> of
  caso1;
  caso2;
else
  caso3;
end;

Un ejemplo simple:
var dia:integer;
begin
  dia:=DayOfWeek(date);
case dia of
  1:Showmessage('Domingo');
  2:Showmessage('Lunes');
  3:Showmessage('Martes');
  4:Showmessage('Miércoles');
  5:Showmessage('Jueves');
  6:Showmessage('Viernes');
  7:Showmessage('Sábado');
  else
  Showmessage('Imposible');
  end;
end;


Diferencias en bucles.

Bucle While.

La estructura de éste en Clipper es
Do While condición_de_entrada
  <instrucciones
EndDo

y en Turbo Pascal

while condicion_de_entrada do begin
  <Instrucciones>
      ...
end;
Para varias instrucciones. Si solamente es una instrucción la que se ejecuta en el bucle sería

while condicion_de_entrada do <Instruccción>;

Bucle For

En Clipper:

For variable=inicio_contador to fin_contador
<Instrucciones>
Next

En Turbo Pascal:

For variable:=valor_inicial to valor_final do begin
  <instrucciones>
        ...
End;
Como en el bucle While, si solamente es una instrucción la que se ejecuta sería
For variable:=valor_inicial to valor_final do <instrucción>;

Bucle Repeat

Aunque es frecuente en varios lenguajes, este no está implementado en Clipper.

La diferencia esencial entre un While y un Repeat es que en el primero, antes de entrar en el mismo, ha de cumplirse la condición de entrada, es decir, puede ser que no se ejecute nunca lo contenido en su estructura, mientras que en el bucle Repeat la condición no es de entrada, sino de salida, con lo que al menos una vez han de ejecutarse sus instrucciones.

Su estructura es

Repeat
  <instrucciones>
       ...
Until condición_de_salida

Con la claúsula Break se fuerza la salida de cualquiera de ellos (el Exit de Clipper) y Continue salta al final de la estrutura (tipo Loop).


Manejo de Bases de Datos.

Es éste un apartado en el que establecer comparaciones no tiene sentido.

Si la forma de trabajo de Clipper es a través de ficheros tipo dBase (dbf) y, como mucho, de texto a través de las funciones FOpen(), FRead(), etc., dejando al margen los creados a través de la instrucción Save, la de Delphi es radicálmente distinta.

Si bien se pueden utilizar los ficheros dbf, o los db de la B.D. de Paradox, puesto que ambos son de los mismos fabricantes de Delphi, Borland, la forma de trabajar con ellos no se parece en nada, e incluso en algunos casos, como el de los índices NTX, no se soportan directamente, sino a través de terceros (como Apollo).

La utilización de éstos cara a redes de area local, no tiene ninguna relación, dado que el entorno de Clipper, MS-DOS y las LAN con las que se "entiende" bien, las Novell, no se asemeja a la programación en Windows, sin interpretar por esto que redes como Netware no sean perfectamente posibles con Delphi.

Bajo este punto de vista, el programador que quiera efectuar el transbase desde un entorno a otro sería aconsejable que se olvidase de lo aprendido en este punto.

Además, mencionando únicamente tablas denominadas planas (como dbf), cuando la arquitectura predominante podría pasar a ser la de Cliente/Servidor e incluso múltiples capas (clientes, servidor de aplicaciones, servidor de datos, terminales Web, etc.). En este entorno, Borland ofrece Interbase como gestor de datos, no obstante está abierto a cualquier otra, Oracle, Sql Server, etc.

En principio Delphi ofrece el acceso a Bases de Datos a través de su motor BDE o a través de la arquitectura de sistemas abiertos de Windows, el ODBC (Open Data Base Conectivity), e incluso a través de motores de datos de terceros.

Lo anterior está referido a las posibilidades de acceder a B.D. En cuanto a la forma de programar para conseguir algún resultado de las tablas es también diferente de manera radical. Aquí el sistema es orientado a objetos y además se puede utilizar tanto el lenguaje estandar de Delphi, como el del BDE mencionado o a través de SQL, con dependencia de lo que se utilice o lo que se pretenda conseguir.

Lo mencionado no debe llevar al desánimo, sino todo lo contrario, pues las herramientas de que dispone Delphi son tan amplias que se puede conseguir practicamente cualquier cosa que se pretenda.

Por poner un ejemplo, vamos a suponer que estamos transfiriendo una aplicación hecha en Clipper y dbf's a Delphi.

El punto conflictivo para empezar sería el de los índices, puesto que los NTX no serían reconocibles sin herramientas ajenas, y habría que traspasarlos a formato MDX.

A partir de aquí es simple. A grandes rasgos y aunque hay muchas formas de realizar lo que queremos, podemos hacernos una idea siguiendo estos puntos:

1. Introducir en el BDE la localización de nuestros ficheros, dando de alta lo que se denomina un alias (no confundir con el termino de Clipper del mismo nombre), es decir, un nombre que usaremos en un futuro para referirnos a nuestras tablas.

2. "Pegar" un componente TTable (por ej.) en nuestro formulario, que en sus propiedades nos pedirá principalmente el nombre del alias creado en el punto 1, de los ficheros que se encuentran en esa ruta a cuál nos estamos refiriendo y si queremos incluir indices. Con esta información es suficiente para que si activamos el componente en su propiedad Active hayamos hecho lo mismo que si ponemos en Clipper Use fichero index indice.

3. A partir de aquí podemos utilizar componentes de visualización, como TDBGrid, similar en cualto al resultado y mucho mas facil de utilizar que los TBrowse y los TBColumn de las versiones 5.x de Clipper, con solo unirlo a la tabla mediante un componente de enlace, TDataSource, editar campos mediante TDBEdit, buscar datos con Locate (entre otros), editar con Edit, imprimir mediante las utilidades de Q.Report que vienen incorporadas a partir de la versión 3 de Delphi o C.Report en versiones anteriores, visualizar en forma de gráficos múltiples mediante el TeeChart, y un largo etcétera.

Es cierto que el ejemplo expuesto es quizás el más simple que se puede encontrar, pero perfectamente válido y funcional. La complejidad tiene que surgir, puesto que las posibilidades de acceso a datos son prácticamente ilimitadas, y lo será en relación a lo que se pretenda conseguir.


¿Dónde ha quedado el Errorsys de Clipper?

Pues todo aquello de modificar el fichero de control de errores, recompilarlo con la aplicación, etc. se ha quedado en nada que se le parezca al funcionamiento y detección de errores en Delphi.

La similitud sería mas facil encontrarla en la clase Error de las versiones de Clipper 5.x, en la cual se genera una llamada a ErrorBlock pudiendose desde aquí crear la función propia de control. El "parecido" puede encontrarse en la llamada y en la protección de un bloque de código (no un CodeBlock) mediante la estructura Begin ... Sequence.

Definidos en el fichero de cabecera ERROR.CH se encuentran los códigos que se generan.


Veamos en Delphi. El tratamiento de los errores se efectúa mediante las denominadas "excepciones". El proceso no es dificil, basta considerar dos tipos de excepciones: las que manda una unidad denominada SysUtils cada vez que se encuentra con un error en ejecución y las que puede lanzar el usuario ante una eventualidad.

La estructura principal de protección del bloque en este caso es Try ... Finally. Veamos un ejemplo

var f:TForm1;
begin
  f:TForm1.Create(Self);
  try
    f.ShowModal;
  finally
    F.Free;
  end;

En el caso de producirse un error en un punto del bloque Try (en este caso al mostrar el form), inmediatamente abandona la ejecución del bloque y pasa el control a Finally, que liberará la memoria.

Esta es la forma más usual de protección ante errores, la otra estructura utilizada es Try ... Except es menos utilizada, salvo que lo que se pretenda sea manejar las excepciones.

  b:=5;
  c:=0;
  try
    a:=b div c;  
  except
// manejadores de excepción
  end;

En un momento cualquiera y dentro del bloque Try se produce un error (en este caso división por cero), inmediatamente se sale y si se ha definido dentro del bloque Except un manejador de errores lo ejecuta. Como no se trata de efectuar un estudio exhaustivo, baste con decir que la estructura sería de este tipo:

try
  a:=b div c;
except
  on Excepción do
  begin
    .......
  end;
end;

donde Excepción definiría el error que la ha provocado, en este caso EZeroDivide. En otros casos sería mas compleja la detección del error en concreto, y se lograría mediante la herencia de la clase Exception, pero no es ese el tema en este momento.

También se especificaba arriba que el usuario puede lanzar una excepción. Esto se consigue sin mas que ejecutando Raise. La utilidad de ello es muy variada e importante, habida cuenta que permite que con su sola ejecución actúen las estructuras Try anteriores o crear una excepción concreta si se pretende esto.

Como la única pretensión es facilitar el camino a los programadores que proviniendo de Clipper "aterrizan" en Delphi, no parece oportuno profundizar más en estas cuestione, en otras preguntas se tratará con más profundidad.

José Luis Freire