Boletín Pascal #1
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
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 eds2004 @ 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-7 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/descarga/p0001.zip ________________________________________________________________________ 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.tdmweb.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/descarga/p0001.zip ________________________________________________________________________ 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: eds2004 @ 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 ________________________________________________________________________ |
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
¿Errores? ¿Omisiones? ¿Comentarios? Por favor contáctanos!






