<-- Capítulo

Índice del tutor de Delphi
© Copyright 1998
por David Martínez.

Todos los derechos reservados

Capítulo -->

Capítulo 3. Objetos, Formas, Unidades y Two-Way Tools

Este capítulo nos ayudará a entender como funciona la POO (programación orientada a objetos), cómo funcionan las formas y unidades en Delphi, y cómo maneja delphi la sincronía entre código y diseño.

Objetos

La programación orientada a objetos ya no es una "moda". Ahora la programación por objetos esta cambiando la forma en que vemos los problemas. Este no es el momento ni el lugar para hablar de la teoría de objetos (un buen libro de tecnologia de objetos de Grady Booch dará una mucha mejor explicación que este curso acerca de lo que es un objeto en programación). Pero es importante saber las bases de lo que es un objeto para saber cómo se aplica a Delphi.

Un objeto es un tipo de datos que incorpora datos y código (comportamiento) en un solo "paquete". Ántes de la era de la orientacion a objetos, el código y los datos eran dos cosas separadas. La orientación por objetos nos permite representar de un modo mucho más conveniente al mundo real.

¿Cómo podemos modelar un objeto del mundo real con objetos "de computadora"? Veamos un ejemplo:

Ejemplo de un Objeto en Object Pascal

Supongamos que estamos haciendo un juego acerca de un Acuario. Para este juego queremos diseñar un Bonito Delfín que va a brincar con el aro y jugar con la pelota, pero queremos en el futuro extender el juego para cualquier tipo de animal marino porque va a ser una simulación bien picuda. Tal como en el mundo real, necesitamos comenzar con un objeto llamado "pescado" (Para los amantes de la biologia: Ya se que el delfín es un mamífero y no un pescado, pero éste es un ejemplo). El objeto pescado tiene sus datos, como son alto, largo, peso y color. Pero el pescado tambien puede hacer otras cosas, como por ejemplo nadar y sumergirse. Entonces primero hacemos nuestro objeto pescado, que tiene la siguiente forma:

  TPescado = class(TObject)
    
Largo : Float;   // El largo del pescado, en centimetros
    
Alto  : Float;   // La altura del pescado, en centimetros
    
Ancho : Float;   // El ancho del pescado, en centimetros
    
Peso  : Float;   // Cuanto pesa el pescado, en gramos
  public
    procedure 
Nadar(AdondeXY, AdondeXZ, AdondeYZ : TPoint);
    procedure 
Sumergirse(Profundidad : Float);
  end
;

Este código le dice a la computadora: TPescado es una clase que hereda de TObject (o "Un Pescado es un Objeto") . Tiene los campos Largo, Alto, Ancho y Peso, que son números de punto flotante. Sus métodos públicos (lo que todo mundo sabe que un pez puede hacer) son Nadar y Sumergirse.

Si esto parece complejo, por favor sigan leyendo. Muy pronto todo quedará claro.

Ahora el Delfín. He decidido para este ejemplo que hay dos clases de delfines, los entrenados y los salvajes (no entrenados). Asi que comencemos con el delfin entrenado:

  TDelfin = class(TPescado)
    
LargodeNariz : Float;     // El largo de la nariz del delfin
  public
    procedure 
TomarAire;
    procedure 
HacerRuido( Decibeles : Integer );
  end
;

Ahora bien, este codigo le dice a la computadora: El TDelfin es una clase que hereda de TPescado (o "Un Delfin es un Pescado"). Esto quiere decir que un Delfin puede hacer todo lo que un pescado puede hacer (tiene largo, alto, ancho, peso y además puede nadar y sumergirse). Entonces yo ya terminé mi delfin, y no tuve que implementar de nuevo las funciones para nadar y sumergirse, que el pescado (y ahora tambien el delfin) puede hacer. Ahora vamos a entrar en la materia del jueguito, el delfin entrenado:

  TDelfinEntrenado = class(TDelfin)
  public
    procedure 
JugarConPelota( Segundos : LongInt; );
    procedure 
BrincarElAro( Circunferencia : Integer );
  end
;

El TDelfinEntrenado es una clase que hereda no de TPescado, sino de TDelfin (o "Un delfín entrenado sigue siendo un delfin, pero..."). Al igual que la vez anterior, un delfín entrenado puede hacer todo lo que un delfin puede hacer, pero además puede jugar con pelota y brincar el aro.

¿Porqué hacer tres objetos nada más para representar un delfín? Supongamos que ahora quiero hacer un tiburón...

  TTiburon = class(TPescado);
    
NumeroDeDientes : LongInt;
    
Bocon : Booolean;
    
ComeHombres : Boolean;
  public
    procedure 
Enojarse;
    procedure 
ComerPersona( Quien : TPersona);
    procedure 
EspantarVisitantes;
  end
;

Gracias a Delphi y la orientación a objetos, ahora estoy "re-utilizando" el código que usé para implementar mi delfín. Ahora, si mañana descubro que los pescados pueden nadar de una manera especial, o quiero hacer la simulación detallada y quiero hacer que hagan algo más, como nacer y morirse, todos los objetos de tipo pescado (tiburon, delfín, delfín entrenado, pez dorado) van a nacer y morir igual que el pescado, porque la implementación aplica al tipo y a todos los tipos de la misma clase. Ahora bien, la "jerarquía" de los objetos que hemos hecho es como sigue:

TObject
   +-----
TPescado
             |
             +-----
Delfin
             |         |
             |         +-----
Delfin Entrenado
             |
             +-----
Tiburon

La jerarquía de objetos es una especie de "árbol genealógico" que nos dice qué objetos se "derivan" de otros objetos. Como conceptualizamos esta jerarquía? Es de hecho bastante sencillo una vez que nos acostumbramos al árbol. Por ejemplo, supongamos que queremos saber que interfaces soporta el "Delfín Entrenado". Leemos todos los "padres" de la siguiente manera: "Un TDelfin ES UN TPescado, que ES UN TObjeto". Esto quiere decir que un objeto de tipo TDelfin soporta todas las interfaces que el objeto Tpescado y el objeto TObject.

TForm vs Form1; Clases vs variables; Concepto Básico de Punteros

Los lectores mas perceptivos han notado que Delphi utiliza una T para especificar que algo es un objeto. De hecho, aqui Delphi se parece mucho a C: Un TObjeto es la definición de la clase del objeto, mientras Objeto1 es una variable de tipo TObjeto. Asi, TForm es la clase, pero la variable que usted usa para sus formas se llama Form1. La variable Button1 es un "puntero" a la instancia de clase TButton.

Supongamos que usted tiene el siguiente código en algun lugar de una forma con un boton (como la que acabamos de escribir). No lo intente, esto es teoría:

var
  
ElBoton : TButton;
begin
  
ElBoton := Button1;
  
Button1.Free;
  
ElBoton.Caption := 'ESTO VA A TRONAR!!!';
end
;

La definición de variable nos dice que "ElBoton" es una variable que apunta a un objeto de clase TBoton. Despues hacemos que ElBoton "apunte" (recordemos que a final de cuentas son punteros) al Button1 de la forma. Después liberamos el Button1. Ahora tratamos de cambiar el texto del botón. Obtendremos una de esas horribles "Fallas de protección general". ¿Por qué?

Pues es muy sencillo. En la linea Button1.Free, la memoria a la que Button1 apunta es liberada. Ya no nos pertenece porque efectivamente "borramos" el botón. Pero ElBoton sigue apuntando a la posición de memoria que Button1 apuntaba. Delphi, aunque es un lenguaje muy elegante, es lo suficientemente poderoso para no preguntarnos cuando queremos manipular memoria (Delphi asume, como C++, que nosotros somos el jefe y sabemos lo que hacemos). Asi que cometemos el error de tratar de cambiar el texto de un objeto que no existe. CRASH!

Lista de Variables               

  Button1 = $01A73F

  ElBoton := Button1, o sea
  
ElBoton := $01A73F

Memoria




$01A73F..$01A7FF (
TButton)

Esto lo menciono porque en cuanto un nuevo programador de Delphi trata de manipular objetos, tarde o temprano se encuentra con un problema de este tipo. Es un error común para el programador de objetos tanto en C como en Pascal, y es la escencia de la "Falla de Protección General", o GPF. El motivo de esta falla es básicamente que estas tratando de utilizar memoria que ya no existe en tu aplicación.

Formas y Unidades

Las formas en Delphi son objetos de tipo "TForm". Delphi viene con una tabla con la "jerarquía" de objetos de la libreria VCL. El programa que diseñamos en el capitulo anterior tenía un objeto tipo "TForm". Este objeto es la ventana estándar de Windows para Delphi. Cuando hicimos nuestro programa con una línea de codigo, usted no tuvo que decirle a TForm como minimizar, maximizar, restaurar, mover o cambiar la ventana de tamaño. Usted sólo le dijo a la computadora: Mi TForm1 es un objeto tipo TForm. Delphi sabe que TForm tiene la capacidad de hacer todo lo anterior (y más). Entonces el comportamiento de todas las formas que usted cree a base de TForm es el mismo. Usted no tuvo que hacer nada para utilizar la magia de TForm - ni siquiera fue necesario saber que TForm era un objeto.

(Delphi tambien le ofrece poder. Si usted quiere hacer cosas muuuy raras, puede usted evitar TForm y utilizar TCustomForm, que es una clase abstracta para que usted haga su formas con comportamientos muy extraños, si se avienta - pero primero hay que aprender más).

Cada forma es guardada en un par de archivos: El archivo PAS (donde escribimos el código) y el archivo DFM (donde diseñamos la forma).

El Archivo PAS

El archivo PAS es un archivo de texto donde escribimos el código de nuestro programa. Delphi nos ayuda a escribir el código, pero eso no quiere decir que tome totalmente el control del editor, o que guarde el archivo en un formato "desconocido". Es un simple archivo de texto escrito en "Object Pascal". El siguiente codigo es el contenido completo del programa que diseñamos.

unit Unit1;

interface

uses
  
Windows, Messages, SysUtils, Classes, Graphics, 
  
Controls, Forms, Dialogs, StdCtrls;

type
  
TForm1 = class(TForm)
    
Button1: TButton;
    procedure 
Button1Click(Sender: TObject);
  private
    
{ Private declarations }
  public
    
{ Public declarations }
  end
;

var
  
Form1: TForm1;

implementation

{$R *.DFM}

procedure 
TForm1.Button1Click(Sender: TObject);
begin
  
ShowMessage('Esto esta Padrisimo!');
end
;

end
.

Tour de dos minutos de Object Pascal

Object Pascal divide los programas en dos secciones: La interfase ( interface) y la implementacion ( implementation ).

Examinemos la interface de este programa: La primera sección nos dice que esta unidad utiliza las librerias Windows, Messages, SysUtils, etc. Despues viene la definición de typos (type), donde Delphi ha definido por nosotros la TForm1 que ha creado (en Delphi, las cosas no solo "aparecen" como en otros lenguajes. Delphi es un lenguaje decente y siempre pone la representación de todo el diseño en archivos legibles y modificables con el editor). Object Pascal es un lenguaje "strong-typed", lo cual quiere decir que cualquier variable, objeto o rutina debe ser declarado antes de ser escrito. La declaración de la forma tambien declara el Botón que le pusimos. Si usted se pone a poner mas botones a la forma, verá que Delphi añade secciones Button2, Button3 a la unidad de la siguiente manera:

type
  
TForm1 = class(TForm)
    
Button1: TButton;
    
Button2: TButton;
    
Button3: TButton;
    procedure 
Button1Click(Sender: TObject);
  private
    
{ Private declarations }
  public
    
{ Public declarations }
  end
;

También podemos ver que Delphi definió el procedimiento Button1Click para cuando el usuario apriete el botón, y este procedimiento pasará el parametro (Sender) lo cual le dirá a nuestra rutina qué objeto fue el que ejecutó el evento (normalmente Button1, pero esto nos da el poder de compartir un "procedimiento manejador de eventos" para mas de un objeto - veremos como hacer esto mas tarde).

Si usted quiere añadir sus propias rutinas manualmente, Delphi ya nos pre-escribio una seccion privada ( private) y publica ( public ) para que podamos añadir funciones manualmente (existen mas directivas, como "protected" o "automated", pero las mas usadas son estas dos, por lo cual Delphi las define por nosotros. Delphi trata de ayudar sin estropear nuestro código con mucha basura que no vamos a usar). Las funciones privadas solo son visibles para TForm1, mientras que las funciones públicas son visibles para cualquier objeto que use TForm1.

Ahora la última seccion. Si usted es muy perceptivo habra notado que he hablado de TForm1 (la clase) pero la variable que inicializaría la clase (de la cual hablamos con anterioridad) no ha sido definida. Bueno, pues la última sección del área de la interface lo define:

var
  
Form1: TForm1;

Aquí le decimos a Delphi que la variable Form1 es una variable de tipo TForm1. ¿Y quién inicializa la variable Form1? Bueno, presione Control-F12 (View Unit=Ver Unidad) y seleccione Project1. Una de las líneas en el archivo de proyecto dice:

Application.CreateForm(TForm1, Form1));

Dentro de la función de TApplication CreateForm, el objeto aplicación crea un TForm1 y lo asigna a la variable Form1. ¿Y porque el objeto aplicación? Esto es porque si Windows quiere matar la aplicacion, la aplicacion necesita tener una lista de todas las formas en tu programa para que puede llamar el metodo "Close" de todas tus formas. TApplication tiene un arreglo (array) llamado Forms donde se encuentran todas las formas de tu proyecto.

Two Way Tools y el archivo DFM

Borland ha perfeccionado una tecnología llamada "Two-way tools". Esto quiere decir que su programa esta escrito en un archivo de texto que usted puede editar inmediatamente, pero al mismo tiempo está representado en los diferentes diseñadores de Delphi. Cada vez que usted añade un objeto, modifica una propiedad o agrega un evento, el IDE "sabe" donde añadir ese objeto, propiedad o evento en la interface, y muchas veces escribe código de inicializacion con bloques "begin-end ", listo para que usted escriba su código y siga su camino. Los "two way tools" de Borland son los mas estables y rápidos del mercado, y son los unicos que le permiten editar la forma en el diseñador o directamente como texto.

En el capítulo anterior, cuando usted hizo doble click en el botón, los "two-way-tools" de Delphi escribieron el código para el evento Click del botón y lo asignaron al mismo.

Regresando a nuestro proyecto, ahora veamos como guarda Delphi las propiedades de nuestro objeto Form1. La forma se guarda en un archivo llamado "DFM" (Delphi Form File), en un formato estilo "res". Pero Delphi procura mantener su arquitectura abierta, asi que nosotros podemos editar el archivo directamente en Delphi. Presione Shift-F12. De la lista de formas que Delphi despliega (View Form), seleccione Form1. Delphi regresa al diseñador de la forma. Ahora presione "Alt-F12". Su unit1.pas desaparece del editor, y en su lugar el texto de unit1.dfm aparece. Su forma tambien desaparece, porque ahora la estamos editando "como texto":

object Form1: TForm1
  
Left = 200
  
Top = 112
  
Width = 1088
  
Height = 750
  
Caption = 'Form1'
  
Font.Charset = DEFAULT_CHARSET
  
Font.Color = clWindowText
  
Font.Height = -11
  
Font.Name = 'MS Sans Serif'
  
Font.Style = []
  
PixelsPerInch = 96
  
TextHeight = 13
  object 
Button1: TButton
    
Left = 240
    
Top = 112
    
Width = 75
    
Height = 25
    
Caption = '&Botoncito'
    
TabOrder = 0
    
OnClick = Button1Click
  end
end

Éste es el texto de nuestra forma. Como verá, todas las propiedades "publicadas" son guardadas aquí. Cada vez que usted cambia una propiedad en el "inspector de objetos", este archivo es modificado. Por ejemplo, cuando hicimos doble-click en el botón para escribir el código de OnClick, Delphi modificó el archivo PAS para declarar la rutina "Button1Click", pero ademas añadió un renglon a esta definición de la forma ( OnClick   =   Button1Click), diciendole al compilador que el evento OnClick del Boton 1 esta enlazado al procedimiento Button1Click. Cuando modificamos el "Caption" del objeto botón, Delphi lo modifico en este archivo. Si quiere Ud. puede modificar el texto "&Botoncito" para que diga "&Botonzote".

Delphi ha escrito bastante código por nosotros, no cree? Pero aún asi, nos permite ver y modificar lo que queramos. En general usted no necesita modificar la forma de esta manera. Pero si lo quiere intentar, tenga cuidado. Delphi puede perder "el hilo" de como esta guardada la forma si usted modifica las secciones con los nombres o tipos de los objetos. Siempre mantenga un respaldo de su DFM antes de modificar la forma como texto.

Para salir de la vista de la forma "Como texto", presione Alt-F12. Delphi. Delphi leerá la forma de nuevo (para interpretar sus cambios) y regresara al diseñador.

Capítulo -->