Boletín Pascal #1
INDICE
1. UNAS PALABRAS DEL EDITOR
2. INTRODUCCION AL OBJECT PASCAL DE BORLAND (III)
CLASES ABSTRACTAS
INTERFACES
3. SUBCLASANDO COMPONENTES
4. VINCULOS
________________________________________________________________________
1. UNAS PALABRAS DEL EDITOR
Tal como anunciáramos en las últimas ediciones del Delphi Newsletter y
del Kylix Newsletter, este Pascal Newsletter ahora reemplaza ambas
publicaciones.
De a poquito estamos construyendo nuestro sitio web con los escasos
recursos de que disponemos. Aún necesita un montón de trabajo, pero ya
pueden visitarlo en:
http://www.latiumsoftware.com/es/index.php
Allí encontrarán los números anteriores de nuestros newsletters y ya
tenemos algunas listas de correo (para Delphi, Free Pascal y Visual
Basic) y pronto abriremos más. En el futuro cercano esperamos poder
ofrecer más contenido: artículos técnicos sobre programación, trucos,
consejos, recursos, componentes, artículos de interés, información,
vínculos, etc.
Para suscribirse a alguna de estas listas de correo en español, por
favor envíe un email en blanco a la dirección de suscripción
correspondiente a la lista:
Delphi <delphi-intermedio-subscribe@gruposyahoo.com>
Free Pascal <freepascal-es-subscribe@yahoogroups.com>
V. Basic <vbasic-es-subscribe@yahoogroups.com>
Después tendrán que responder a un mensaje de confirmación que recibirán
unos segundos después de suscribirse y entonces recibirán un mensaje de
bienvenida explicando cómo enviar mensajes a la lista y otras cosas. Es
muy sencillo, pero si necesitan más información por favor visiten
nuestro sitio web o contáctenos por email.
Atentamente,
Ernesto De Spirito
eds2008 @ latiumsoftware.com
________________________________________________________________________
JfControls Lib. Multilenguaje. Multiapariencia. Skins. Privilegios. Más
de 40 componentes integrados y personalizables. Múltiples problemas de
programación resueltos. Administración centralizada de recursos. Para
Delphi 3-2006 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm
________________________________________________________________________
2. INTRODUCCION AL OBJECT PASCAL DE BORLAND (III)
Este artículo es la continuación de una serie de artículos que comenzá-
ramos en el Kylix Newsletter, destinado a proveer una introducción
rápida al Object Pascal de Borland para principiantes. Asumimos que el
lector tiene experiencia de programación en algún otro lenguaje de POO
como C++ o Java y que ya entiende conceptos básicos como encapsula-
miento, herencia y polimorfismo.
CLASES ABSTRACTAS
Delphi permite la definición de clases abstractas. ¿Qué es una clase
abstracta? Una clase abstracta es una clase no pensada para ser instan-
ciada sino subclasada, así que no puede crear objetos de esta clase,
pero puede derivarla para crear clases descendientes, ya sean abstractas
o no.
Recordemos que en el artículo precedente de esta serie definimos una
clase polimórfica que tenía un método virtual sobredefinido por sus
descendientes:
interface
type
interface
type
TFigura = class(TObject)
procedure Mostrar; virtual;
end;
TCirculo = class(TFigura)
procedure Mostrar; override;
end;
TCuadrado = class(TFigura)
procedure Mostrar; override;
end;
TOvalo = class(TCirculo)
procedure Mostrar; override;
end;
implementation
procedure TFigura.Mostrar; begin ShowMessage('Figura'); end;
procedure TCirculo.Mostrar; begin ShowMessage('Circulo'); end;
procedure TCuadrado.Mostrar;begin ShowMessage('Cuadrado'); end;
procedure TOvalo.Mostrar; begin ShowMessage('Ovalo'); end;
Una variable de tipo TFigura, por ejemplo Figura, puede contener una
referencia no sólo a un objeto TFigura, sino también a un descendiente
de TFigura (como TCirculo, TCuadrado y TOvalo). Dado que el método
Mostrar es virtual (no estático), cuando se llama a Figura.Mostrar, será
ejecutado el método correspondiente a la clase del objeto que Figura
referencia al momento de la llamada.
var
Figura: TFigura;
Circulo: TCirculo;
Cuadrado: TCuadrado;
Ovalo: TOvalo;
begin
// Primero creamos los objetos
Circulo := TCirculo.Create;
Cuadrado := TCuadrado.Create;
Oval := TOval.Create;
Figura := Circulo; // Figura ahora referencia un objeto TCirculo
Figura.Mostrar; // Llama a TCirculo.Mostrar
Figura := Cuadrado; // Figura ahora referencia un objeto TCuadrado
Figura.Mostrar; // Llama a TCuadrado.Mostrar
Figura := Ovalo; // Figura ahora referencia un objeto TOvalo
Figura.Mostrar; // Llama a TOvalo.Mostrar
// Libera los objetos
Circulo.Free; Cuadrado.Free; Ovalo.Free;
end;
Ahora bien, ¿Qué tiene que ver esto con las clases abstractas? Bueno,
supongamos que el método Mostrar fuera tan específico de los
descendientes de TFigura como para implementarlo en una clase tan
general como lo es TFigura. Delphi permite la creación de una clase
dejando algunos de sus métodos (virtuales o dinámicos) sin definir.
Por ejemplo, podríamos declarar TFigura del siguiente modo:
TFigura = class(TObject)
procedure Mostrar; virtual; abstract;
end;
Al declarar un método como abstracto, entonces la clase es abstracta y
no puede ser instanciada, pero sí derivada:
TCirculo = class(TFigura)
procedure Mostrar; override;
end;
TCuadrado = class(TFigura)
procedure Mostrar; override;
end;
TOvalo = class(TCirculo)
procedure Mostrar; override;
end;
implementation
procedure TCirculo.Mostrar; begin ShowMessage('Circulo'); end;
procedure TCuadrado.Mostrar;begin ShowMessage('Cuadrado'); end;
procedure TOvalo.Mostrar; begin ShowMessage('Ovalo'); end;
Nótese que esta vez no definimos TFigura.Mostrar ya que es un método
abstracto. El ejemplo que mostramos arriba funcionará exactamente
igual. Sólo noten que podemos declarar una variable del tipo TFigura
var Figura: TFigura;
pero que no podemos crear objetos de esa clase:
Figura := TFigura.Create; // Produciría un error de compilación
El beneficio de las clases abstractas es que permiten estableces clases
bases para polimorfismo sin tener que proveer implementaciones prede-
terminadas para métodos que generalmente son muy específicos de los
descendientes como para ser conocidos de antemano al definir la clase.
Por ejemplo puede definir una pila de cadenas de abstracta:
TCustomStringStack = class
procedure clear; virtual; abstract;
procedure push(s: string); virtual; abstract;
function pop: string; virtual; abstract;
function top: string; virtual; abstract;
end;
Los descendientes de TCustomStringStack tendrán por lo menos esos
métodos y pueden implementar la pila de cualquier manera. Por ejemplo
puede tener una pila basada en un arreglo (que la podríamos llamar
TArrayStringStack), o en una lista enlazada (TListStringStack) o en un
fichero en disco (TDiskStringStack), pero independientemente de la
implementation (no conocida al momento de definir la clase base
abstracta), usted sabrá que una variable de tipo TCustomStringStack
puede manejar las operaciones básicas de todos estos tipos de pilas
(TArrayStringStack, TListStringStack o TDiskStringStack) y que es la
asignación es compatible con ellas.
La VCL está llena de clases abstractas (normalmente tienen la palabra
"Custom" en sus nombres) y muchas propiedades que son objetos son del
tipo de una clase abstracta.
INTERFACES
Las interfaces son un poco como las clases, excepto que:
* Son declaradas con la palabra 'interface' en lugar de 'class'.
* Sus nombres generalmente comienzan con 'I' en vez de con 'T'.
* No tienen constructores ni destructores.
* No pueden ser instanciadas.
* No pueden tener campos: sólo métodos y propiedades.
* Como no tienen campos, los especificadores read y write de las
propiedades sólo pueden ser métodos.
* Los métodos no pueden ser declarados como virtuales, dinámicos,
abstractos o sobredefinidos. Se podría decir que son abstractos.
* La convención de llamada predeterminada es register. Use stdcall para
interfaces compartidas entre módulos (especialmente si están escritas
en diferentes lenguajes). Use safecall para implementar métodos de
interfaces duales o interfaces CORBA.
* No se permiten especificadores de visibilidad (public, private,
protected y published). Todos los miembros son públicos.
* Son descendientes de IUnknown (el equivalente de TObject).
* Pueden tener un GUID (identificador global único) para identificar
unívocamente una interface. Se usa al consultar una interface para
obtener referencias a sus implementaciones. Un GUID es un valor de
16 bytes (vea el ejemplo).
Esta es una declaración de una interfaz:
type
IRotacion = interface(IUnknown)
['{01234567-89AB-CDEF-0123-456789ABCDEF}']
procedure Rotar(grados: single); stdcall;
end;
El propósito de una interfaz es eventualmente ser implementada por una
clase. También puede servir como interfaz base para sus descendientes.
Una clase puede implementar muchas interfaces. Por ejemplo, nuestra
clase TFigura puede implementar la interfaz IRotacion que acabamos de
declarar:
type
TFigura = class(TObject, IRotacion)
procedure Mostrar; virtual, abstract override;
procedure Rotar(grados: single); stdcall;
function QueryInterface(const IID: TGUID; out Obj):
HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
¿De dónde salieron todos esos métodos? Bueno, una clase que implementa
interfaces debe declarar e implementar todos sus métodos. En este caso,
todos los métodos de IRotacion deberían ser implementados. IRotacion
tiene 4 métodos: Rotar y 3 métodos que hereda de IUnknown. Si no
quisiéramos implementar esos métodos, podemos derivar de la clase
TInterfacedObject (un descendiente directo de TObject) o de TComponent
(un ancestro común de muchos componentes visuales y no visuales) dado
que ya implementan esos tres métodos, de modo que para evitar tener que
hacerlo nosotros mismos, la manera más fácil de implementar una interfaz
es usando una clase derivada de una de esas dos clases o una de sus
descendientes. Por ejemplo:
TFigura = class(TInterfacedObject, IRotacion)
procedure Mostrar; virtual; abstract;
procedure Rotar(grados: single); stdcall;
end;
De esta forma sólo nos tenemos que preocupar de Rotar. Un método imple-
mentado puede ser virtual y abstracto, así que si queremos podemos
dejarle la implementación a los descendientes de TFigura:
interface
type
TFigura = class(TInterfacedObject, IRotacion)
procedure Mostrar; virtual; abstract;
procedure Rotar(grados: single); virtual; stdcall; abstract;
end;
TCuadrado = class(TFigura)
procedure Mostrar; override;
procedure Rotar(grados: single); override; stdcall;
end;
TOvalo = class(TCuadrado)
procedure Mostrar; override;
procedure Rotar(grados: single); override; stdcall;
end;
implementation
procedure TCuadrado.Mostrar; begin ShowMessage('Cuadrado'); end;
procedure TCuadrado.Rotar(grados: single);
begin ShowMessage('Cuadrado Rotado'); end;
procedure TOvalo.Mostrar; begin ShowMessage('Ovalo'); end;
procedure TOvalo.Rotar(grados: single);
begin ShowMessage('Ovalo Rotado'); end;
Ahora podemos escribir un código como este:
var
Cuadrado: TCuadrado;
Ovalo: TOvalo;
Rotacion: IRotacion;
begin
Cuadrado := TCuadrado.Create;
Ovalo := TOvalo.Create;
Rotacion := Cuadrado;
Rotacion.Rotar(45); // Llama a TCuadrado.Rotar
Rotacion := Ovalo;
Rotacion.Rotar(90); // Llama a TOvalo.Rotar
end;
Como puede ver, el uso de interfaces es muy similar al polimorfismo. La
diferencia es que es más abierto ya que a una variable de tipo interfaz
le puede asignar un objeto de CUALQUIER clase con la condición que dicha
clase implemente la interfaz, no con la condición que sea derivada de
una cierta clase base. Por ejemplo, si tuviéramos otra clase no descen-
diente de TFigura que implementara IRotacion, como esta:
TMiBoton = class(TComponent, IRotacion)
procedure Rotar(grados: single); stdcall;
end;
Entonces estas sentencias también serían válidas:
MiBoton := TMiBoton.Create;
Rotacion := MiBoton;
Rotacion.Rotar(180); // Llama a TMiBoton.Rotar
Hay un par de cosas importantes que se deben conocer acerca de las
interfaces y de la implementación de los 3 métodos de IUnknown hecha por
TInterfacedObject. Primero, los objetos cuentan las referencias,
significando ésto que cuando realiza una asignación, el valor de la
cuenta se incrementa (_AddRef es llamado implícitamente). Este valor se
almacena en TInterfacedObject.FRefCount y después que crea un objeto se
establece en cero. Por ejemplo, después de
Cuadrado := TCuadrado.Create;
Cuadrado.FRefCount será cero. Después de una asignación como
Rotacion := Cuadrado;
Cuadrado.FRefCount será 1. Cuando asigne otro objeto a Rotacion, como
Rotacion := Ovalo;
la cuenta se decrementa (_Release es llamado implícitamente), y si se
hace cero, el objeto es liberado. Eso explica por qué no liberamos
Cuadrado en el ejemplo de más arriba. Tampoco liberamos Ovalo porque al
salir de un procedimiento o función, a las variables locales de interfaz
es como si se les asignara nil, así que la cuenta es decrementada alcan-
zando cero y entonces el objeto es liberado.
Algunas veces este comportamiento puede ser una molestia. ¿Qué sucedería
si necesitáramos el objeto después de asignar la variable de interfaz a
otro objeto? Bueno, tenemos dos opciones: alterar el valor de FRefCount
o definir un nuevo objeto como TInterfacedObject pero implementando los
métodos de IUnknown de una manera diferente.
Para la primera opción, el procedimiento del ejemplo anterior sería:
var
Cuadrado: TCuadrado;
Ovalo: TOvalo;
Rotacion: IRotacion;
begin
Cuadrado := TCuadrado.Create;
Ovalo := TOvalo.Create;
InterlockedIncrement(Cuadrado.FRefCount);
Rotacion := Cuadrado;
Rotacion.Rotar(45); // Llama a TCuadrado.Rotar
InterlockedIncrement(Ovalo.FRefCount);
Rotacion := Ovalo;
InterlockedDecrement(Cuadrado.FRefCount);
Rotacion.Rotar(90); // Llama a TOvalo.Rotar
Rotacion := nil;
InterlockedDecrement(Ovalo.FRefCount);
Cuadrado.Free;
Ovalo.Free;
end;
Para la segunda opción, podríamos definir una clase TInterfacedObj:
interface
type
TInterfacedObj = class(TObject, IUnknown)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult;
stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
implementation
function TInterfacedObj.QueryInterface(const IID: TGUID; out Obj):
HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := HResult($80004002); // E_NOINTERFACE
end;
function TInterfacedObj._AddRef: Integer; begin Result := 1; end;
function TInterfacedObj._Release: Integer; begin Result := 1; end;
TFigura debería ser derivada de TInterfacedObj:
TFigura = class(TInterfacedObj, IRotacion)
...
Y nuestro procedimiento de prueba sería como este:
var
Cuadrado: TCuadrado;
Ovalo: TOvalo;
Rotacion: IRotacion;
begin
Cuadrado := TCuadrado.Create;
Ovalo := TOvalo.Create;
Rotacion := Cuadrado;
Rotacion.Rotar(45); // Llama a TCuadrado.Rotar
Rotacion := Ovalo;
Rotacion.Rotar(90); // Llama a TOvalo.Rotar
Cuadrado.Free;
Ovalo.Free;
end;
Esta parece una forma más "natural" de programar.
________________________________________________________________________
3. SUBCLASANDO COMPONENTES
En la última edición del Delphi Newsletter estábamos desarrollando una
sencilla aplicación para buscar ficheros. En el ejemplo, colocamos los
ficheros coincidentes con el criterio de búsqueda en un componente
TListView. Si el usuario hacía doble-clic en un fichero, abríamos el
documento con la aplicación asociada, y si el usuario hacía doble-clic
en el camino de la carpeta, entonces abríamos el directorio con el
Explorador de Windows. Vimos que el componente TListView no tiene
propiedades y métodos para conocer directamente cual columna fue
"cliqueada", así que tuvimos que meternos en algo de problemas para
determinar eso. Tal vez podría ser una mejor idea hacer un nuevo
TListView capaz de brindarnos esa clase de información, así podemos
usarlo en otros programas.
Por supuesto, no vamos a hacer un nuevo componente de la nada, sino que
vamos a subclasar o derivar un nuevo componente de TListView, así que
nuestro trabajo será mínimo.
En el menú "Componentes", elegimos "Nuevo componente...". En el diálogo
Nuevo Componente elegimos TListView como el tipo del ancestro,
escribimos ListViewX como el nombre de nuestra nueva clase, le damos el
camino para la unidad en el cuadro de texto correspondiente y hacemos
clic en OK. Ahora tenemos una nueva unidad. Podríamos haberlo hecho a
mano ya que no es ninguna ciencia como se puede apreciar. Bien, ahora
todo lo que tenemos que hacer es crear una versión mejorada de
GetItemAt que nos dé más información. Hay muchas formas de hacerlo,
pero lo que vamos a hacer es agregar un tercer parámetro pasado por
referencia a un nuevo procedimiento que llamaremos GetItemAtX.
type
TListViewX = class(TListView)
public
function GetItemAtX(X, Y: integer; var Col: integer): TListItem;
end;
Ahora implementaremos ese procedimiento:
implementation
function TListViewX.GetItemAtX(X, Y: integer;
var Col: integer): TListItem;
var
i, n, RelativeX, ColStartX: Integer;
ListItem: TlistItem;
begin
Result := GetItemAt(X, Y);
if Result <> nil then begin
Col := 0; // Primera columna
end else if (ViewStyle = vsReport) and (TopItem <> nil) then begin
// Primero, trataremos de encontrar la fila
ListItem := GetItemAt(TopItem.Position.X, Y);
if ListItem <> nil then begin
// Ahora trataremos de encontrar la columna
RelativeX := X - ListItem.Position.X - BorderWidth;
ColStartX := Columns[0].Width;
n := Columns.Count - 1;
for i := 1 to n do begin
if RelativeX < ColStartX then break;
if RelativeX <= ColStartX
+ StringWidth(ListItem.SubItems[i-1]) then begin
Result := ListItem; Col := i;
break;
end; // if
Inc(ColStartX, Columns[i].Width);
end; // for
end; // if
end; // if
end;
No es necesario que entiendan este código para entender cómo subclasar
un componente. Lo importante es que hemos declarado e implementado un
nuevo método.
Ahora es tiempo de agregar nuestro nuevo componente a la paleta de
componentes. En el procedimiento register noten que que nuestro nuevo
componente será agregado a la paleta Samples. Pueden cambiar esto si
quieren. Para instalar el componente vayan al menú "Componentes" y
elijan "Instalar Componente...". Debería aparecer un cuadro de diálogo.
Simplemente hagan clic en OK si quieren que este control sea parte del
paquete de usuario estándar. Otro diálogo debería aparecer mostrándonos
el paquete. Haga clic en "Compilar". Si esta es la primera vez que
compila un paquete, le debería aparecer un diálogo preguntando si desea
continuar. Haga clic en "Sí". Si no hubo errores, el paquete se ha
compilado con éxito y el componente se ha agregado a la paleta. Ahora
puede cerrar la ventana del paquete (guarde los cambios cuando se le
pregunte).
Ahora, ¿cómo cambiamos el viejo TListView por el nuevo TListViewX en
nuestra aplicación de búsqueda de ficheros? Haga clic con el botón
derecho sobre el formulario y elija Ver como Texto en el menú
contextual. Verá una representación en texto de sus formulario. Busque
TListView y reemplácelo por TListViewX. Haga clic con el botón derecho
sobre el código y elija Ver como Formulario para volver a la represen-
tación visual. El atajo de teclado para alternar entre estas dos
representaciones es [Alt+F12]. En el código de la unidad también
debería reemplazar TListView por TListViewX y agregar ListViewX a la
cláusula Uses.
Ya que tenemos un nuevo método que podemos usar, nuestro evento
DblClick debería ser mucho más fácil de entender:
procedure TForm1.ListView1DblClick(Sender: TObject);
var
Col: Integer;
ListItem: TListItem;
begin
ListItem := ListView1.GetItemAtX(Last.X, Last.Y, Col);
if ListItem <> nil then begin
if Col = 0 then begin
if ShellExecute(Self.Handle, nil,
PChar(ListItem.SubItems.Strings[0] + ListItem.Caption),
nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin
Application.MessageBox('No se pudo ejecutar la aplicación',
'Error', MB_ICONEXCLAMATION);
end; // if
end else if Col = 1 then begin
if ShellExecute(Self.Handle, 'explore',
PChar(ListItem.SubItems.Strings[0]),
nil, nil, SW_SHOWMAXIMIZED) <= 32 then begin
Application.MessageBox('No se pudo ejecutar la aplicación',
'Error', MB_ICONEXCLAMATION);
end; // if
end; // if
end; // if
end;
Ya puede probar la aplicación para verla funcionar. El código fuente
completo se encuentra disponible en nuestro sitio web:
http://www.latiumsoftware.com/es/file.php?id=p01
________________________________________________________________________
4. VINCULOS (LINKS)
Sebastián nos envió este vínculo donde pueden ver algunas capturas de
pantalla de Delphi para Linux (parte del proyecto Kylix):
Dr. Bob
http://www.drbob42.com/Kylix/hotshots.htm
¿Tiene preguntas? Visite este sitio:
UDDF - Unofficial Delphi Developers FAQ
http://www.ellipse-data.com/delphifaq/devfaq/indexnf.html
________________________________________________________________________
Si no has recibido el archivo con el código fuente completo de los
ejemplos que se presentan en este boletín, puedes descargarlo de la
siguiente dirección: http://www.latiumsoftware.com/es/file.php?id=p01
________________________________________________________________________
Página principal: http://www.latiumsoftware.com/es/pascal/index.php
Página del grupo: http://espanol.groups.yahoo.com/group/boletin-pascal/
Para suscribirse / apuntarse: boletin-pascal-subscribe@gruposyahoo.com
Para cancelar / removerse: boletin-pascal-unsubscribe@gruposyahoo.com
Para reportar problemas con la suscripción: eds2008 @ latiumsoftware.com
________________________________________________________________________
Este boletín se provee "TAL Y COMO ESTA", sin garantía de ninguna clase.
Su uso implica la aceptación de nuestros términos de licencia y de la
ausencia de garantía que puedes leer en nuestro sitio web. Allí también
encontrarás una nota sobre marcas registradas. Te animamos a que redis-
tribuyas este boletín, siempre y cuando lo hagas en forma completa
(incluyendo la información de copyright), sin modificaciones y de manera
gratuita. Los artículos son copyright de sus respectivos autores y se
reproducen aquí con el permiso de los mismos.
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/es/index.php
Copyright (c) 2000 por Ernesto De Spirito. Todos los derechos reservados
________________________________________________________________________
|