Boletín Pascal #8
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
Boletín Pascal #8 INDICE 1. UNAS PALABRAS DEL EDITOR 2. KYLIX FAQ (PREGUNTAS FRECUENTES) - ¿Cuándo estará listo? - ¿En cuáles distribuciones de Linux funcionará? - ¿KDE o GNOME? - ¿Estará disponible para otras plataformas? - ¿Cambiará el lenguaje? - ¿Qué tipo de binarios producirá? - ¿Podré enlazar archivos objeto compilados con gcc o gpp a mis proyectos Delphi? - ¿Qué es CLX? 3. PROGRAMACION DE BASES DE DATOS EN CODIGO (II) - SET RELATION - INDEX ON - CONSULTAS - SELECT - SELECT - FROM - WHERE - GROUP BY - HAVING - ORDER BY - JOINs - PARAMETROS - EJEMPLOS 4. ENLACES ________________________________________________________________________ 1. UNAS PALABRAS DEL EDITOR Hay nuevos trucos en nuestra sección de Trucos Delphi: ¿Cómo ... * Determinar la longitud real de una cadena? * Buscar archivos recursivamente en el disco duro? * Obtener el icono de una aplicación o documento? * Establecer el color invisible de una imagen transparente? * Imitar a Deltree.exe? * Obtener los directorios importantes de Windows? * Comparar una cadena con un patrón? * Determinar si un nombre de archivo coincide con una especificación? * Establecer el papel tapiz del escritorio? * Usar cursores propios? * Crear automáticamente un formulario bajo petición? * Hacer que MessageDlg ejecute el sonido correspondiente? * Ejecutar un sonido propio? * Establecer la impresora predeterminada? * Impedir que el usuario cierre un formulario? * Crear un archivo temporal único? * Saber si hay un disco/diskette/CD en una unidad de discos removibles? * Obtener las fechas del primer y último día del mes de una fecha dada? * Mostrar el Panel de Control o ejecutar una "applet" del Panel de Control? Aquí están las respuestas: http://www.latiumsoftware.com/es/delphi/index.php Además ponemos a disposición de los interesados el archivo de recursos traducido al español del componente TZipMaster que presentáramos en el Boletín Pascal #5. http://www.latiumsoftware.com/download/zipmsges.zip Para usarlo deben descomprimir el fichero ZipMsgES.res que se encuentra dentro del Zip y ponerlo en el directorio donde pusieron los demás archivos de la VCL que venían con el componente (por ejemplo en "C:\Delphi\Zip\VCL" si siguieron el ejemplo). En los programas deben cambiar el nombre del archivo de recursos (que antes era ZipMsgUS.res para los mensajes en inglés y ahora será ZipMsgES.res). Saludos, 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. KYLIX FAQ (PREGUNTAS FRECUENTES) * ¿Cuándo estará listo? Cuando Borland estaba por juntar fuerzas con Corel, el lanzamiento de Kylix estaba planeado para mediados de año. Como ya saben, la fusión se canceló y Borland continuó trabajando solo en Kylix. Ahora el lanzamiento probablemente sucederá antes de fin de año (eso esperamos). Mientras tanto, la versión beta ya ganó dos premios: http://www.borland.com/kylix/ * ¿En cuáles distribuciones de Linux funcionará? En todas. * ¿KDE o GNOME? Kylix funcionará con KDE, GNOME y simplemente fvwm. Sin embargo, la primera versión está mejor preparada para KDE, pero correrá también en GNOME, sólo que no hará uso completo de las características especí- ficas de GNOME. * ¿Estará disponible para otras plataformas? No por ahora (solamente PCs Intel o compatible con Windows o Linux). En el futuro puede que se expanda a otras plataformas de hardware y/o sistemas operativos. * ¿Cambiará el lenguaje? Sólo mínimamente. * ¿Qué tipo de binarios producirá? Programas y objetos compartidos ELF estándar nativos e independientes. * ¿Podré enlazar archivos objeto compilados con gcc o gpp a mis proyectos Delphi? Sí para los archivos objeto de gcc y no para los de g++ (tendrán que recompilarse en C++ Builder para Linux cuando esté disponible). * ¿Qué es CLX? CLX (que se pronuncia "kliks") viene de "biblioteca de componentes para plataformas intercambiables" y es una nueva estructura fácilmente extensible basada en componentes para objetos gráficos, componentes de base de datos, componentes de red, etc. Suena bastante parecido a la VCL, ¿verdad? Ciertamente que sí, excepto que CLX será independiente de la plataforma, de modo que las aplicaciones que usen CLX serán más portables. ________________________________________________________________________ 3. PROGRAMACION DE BASES DE DATOS EN CODIGO (II) SET RELATION ============ En xBase: SELECT 0 USE customer ALIAS Clientes INDEX CustNo SELECT 0 USE orders ALIAS Ordenes SET RELATION TO CustNo INTO Clientes En Delphi es un poco más complicado: var Ordenes, Clientes: TTable; Maestra: TDataSource; begin // Abrir las tablas CreateAndOpen(Ordenes, 'DBDEMOS', 'orders.db'); CreateAndOpen(Clientes, 'DBDEMOS', 'customer.db'); // Crear un DataSource para la tabla maestra Maestra := TDataSource.Create(nil); Maestra.DataSet := Ordenes; // Establecer la relación maestro-detalle Clientes.MasterSource := Maestra; Clientes.MasterFields := 'CustNo'; Para crear una relación maestro-detalle, primero necesitamos un DataSource (origen de datos) de la tabla que será la "tabla maestra" (Ordenes en el ejemplo de arriba) y luego establecemos las propiedades MasterSource y MasterFields de la "tabla detalle" (Clientes en el ejemplo de arriba). MasterSource es el DataSource de la tabla maestra, mientras que MasterFields es una cadena listando los nombres de los campos (separados por punto y coma) en la tabla maestra sobre los cuales se hará la relación. El índice activo de la tabla detalle debe comenzar al menos con los campos correspondientes en la tabla detalle (aunque puede tener más campos). En el ejemplo, la clave primaria de Clientes está basada en 'CustNo' y como es la clave primaria, es el índice activo de modo predeterminado. En una relación uno-a-muchos, normalmente la tabla del lado del "uno" es la tabla maestra y la del lado del "muchos" es la tabla detalle, pero se puede elegir libremente. En el ejemplo de arriba hemos considerado la "tabla muchos" (Ordenes) como la tabla maestra. Esto significa que cada vez que nos movamos en la tabla Ordenes, el puntero de registro de la tabla Clientes se reposicionará en el primer (y único) registro correspondiente que concuerde con el criterio Clientes.CustNo = Orders.CustNo En xBase uno puede moverse "libremente" por todos los registros de la tabla detalle si quiere, pero en Delphi la tabla detalle está afectada por un rango de claves, de modo que sólo aquellos registros que cumplen el criterio son accesibles. Aquí va un ejemplo más completo: uses Db, DBTables; procedure CreateAndOpen(var tabla: TTable; DatabaseName, TableName: string); begin if tabla <> nil then FreeAndNil(tabla); tabla := TTable.Create(nil); tabla.DatabaseName := DatabaseName; tabla.TableName := TableName; tabla.Open; end; procedure TForm1.Button1Click(Sender: TObject); var Ordenes, Clientes: TTable; Maestra: TDataSource; Total: Currency; begin Ordenes := nil; Clientes := nil; Maestra := nil; try // Abrir las tablas CreateAndOpen(Ordenes, 'DBDEMOS', 'orders.db'); CreateAndOpen(Clientes, 'DBDEMOS', 'customer.db'); // Crear un DataSource para la tabla maestra Maestra := TDataSource.Create(nil); Maestra.DataSet := Ordenes; // Establecer la relación maestro-detalle Clientes.MasterSource := Maestra; Clientes.MasterFields := 'CustNo'; ShowMessage(Format('%d=%d --> %s', [ Ordenes.FieldByName('CustNo').AsInteger, Clientes.FieldByName('CustNo').AsInteger, Clientes.FieldByName('Company').AsString ])); Ordenes.Next; ShowMessage(Format('%d=%d --> %s', [ Ordenes.FieldByName('CustNo').AsInteger, Clientes.FieldByName('CustNo').AsInteger, Clientes.FieldByName('Company').AsString ])); Clientes.Next; if Clientes.Eof then ShowMessage('EOF'); // Cancelar la relación Clientes.MasterSource := nil; // Establecer la relación inversa Maestra.DataSet := Clientes; Ordenes.IndexName := 'CustNo'; Ordenes.MasterSource := Maestra; Ordenes.MasterFields := 'CustNo'; Clientes.First; // 'Kauai Dive Shoppe' // Totalizar el monto pagado por este cliente Total := 0; while not Ordenes.Eof do begin Total := Total + Ordenes.FieldByName('AmountPaid').AsCurrency; Ordenes.Next; end; ShowMessage(Format('%s %m', [ Clientes.FieldByName('Company').AsString, Total ])); // Debería ser 'Kauai Dive Shoppe $ 51.450,80' except Ordenes.Free; Clientes.Free; Maestra.Free; raise; end; Ordenes.Free; Clientes.Free; Maestra.Free; end; INDEX ON ======== En la edición pasada mostramos cómo agregar un índice a una tabla nueva. Aquí mostraremos cómo agregar un índice a una tabla existente, y también como removerlo: procedure TForm1.Button2Click(Sender: TObject); var Clientes: TTable; begin Clientes := nil; try // Abrir las tablas CreateAndOpen(Clientes, 'DBDEMOS', 'customer.db'); with Clientes do begin AddIndex('Ubicacion', 'Country;State', [ixCaseInsensitive, ixNonMaintained]); IndexName := 'Ubicacion'; First; ShowMessage(FieldByName('Company').AsString); // Unisco IndexName := ''; Close; Exclusive := True; Open; DeleteIndex('Ubicacion'); end; except Clientes.Free; raise; end; Clientes.Free; end; Un índice se agrega con el método AddIndex. En xBase, después de crear un índice, éste se convierte en el índice activo, pero aquí debemos hacerlo explícitamente si eso es lo que se quiere. La opción ixNonMaintained se usa normalmente para índices temporarios y significa que este índice no será actualizado cuando agregue, borre y modifique registros a menos que sea el índice activo. Estos índices se reindexan automáticamente cada vez que se convierten en el índice activo si es que están desactualizados. Para remover un índice usamos el método DeleteIndex. La tabla tiene que estar abierta con acceso exclusivo para que funcione. No se puede remover una clave primaria. CONSULTAS ========= Hay dos tipos de consultas: * Consultas de selección o de resultados: Se usan para obtener ciertas filas (registros) y columnas (campos) de una o más tablas. Puede trabajar con el resultado de una consulta de modo similar al que trabaja con una tabla. * Consultas de acción: Se usan para realizar una acción sobre una o más tablas de una base de datos, como actualizar registros, borrar registros, etc. En esta edición vamos a tratar las consultas de selección y dejaremos las consultas de acción para el próximo número. Comencemos con un ejemplo. Supongamos que queremos ver el número de cliente, nombre del cliente y monto total abonado por nuestros clientes de los EE.UU. con montos totales superiores a los $ 100.000, y que queremos el listado ordenado alfabéticamente (por nombre de compañía)... procedure TForm1.btnQueryClick(Sender: TObject); var Consulta: TQuery; begin Consulta := nil; try Consulta := TQuery.Create(nil); with Consulta do begin DatabaseName := 'DBDEMOS'; with SQL do begin Add('SELECT Orders.CustNo, Company,'); Add(' SUM(AmountPaid) As TotalAmount'); Add('FROM Orders, Customer'); Add('WHERE Orders.CustNo = Customer.CustNo'); Add(' AND Country = "US"'); Add('GROUP BY Orders.CustNo, Company'); Add('HAVING SUM(AmountPaid) > 100000'); Add('ORDER BY Company'); end; Open; while not Eof do begin ShowMessage(Format('%d %s %m', [ FieldByName('CustNo').AsInteger, FieldByName('Company').AsString, FieldByName('TotalAmount').AsCurrency ])); Next; end; end; except Consulta.Free; raise; end; Consulta.Free; end; Como puede ver, una consulta de resultados es como una tabla, siendo la principal diferencia que en lugar de tener una propiedad TableName tiene una propiedad SQL que es una lista de cadenas donde puede escribir una sentencia SQL (más sobre esto después). En el ejemplo, el resultado de esta sentencia SQL es como una tabla con tres campos y cuatro registros: CustNo Company TotalAmount 3053 American SCUBA Supply $ 183.094,40 1563 Blue Sports $ 165.245,45 3042 Gold Coast Supply $ 132.233,00 1560 The Depth Charge $ 126.889,35 SELECT ====== Toda la magia la hace la sentencia SQL SELECT. Si no le es familiar, aquí le daremos una introducción rápida para iniciarlo. Puede encontrar más información en el archivo de ayuda Local SQL Help que viene con la BDE. SELECT ------ La sentencia comienza con la palabra reservada SELECT y a continuación debemos especificar la lista de columnas (campos) que queremos de la/s tabla/s base. En nuestro ejemplo queríamos tres columnas: Orders.CustNo, Company, SUM(AmountPaid) AS TotalAmount Hay un campo llamado CustNo en ambas tablas que queremos acceder, así que tuvimos que calificar el nombre especificando de cuál tabla lo queremos tomar. Las columnas pueden ser campos o expresiones, incluyendo expresiones "agregadas" -como SUM- que funcionan con un grupo de registros (ver GROUP BY). La palabra "AS" sirve para especificar el nombre de una columna en el cursor resultado. Si quisiéramos todos los campos de una tabla podemos usar una sintaxis como la siguiente: Orders.* FROM ---- Especifica la lista de tablas de las cuales se tomarán los datos. No está restringido a las tablas de una sola base de datos ni a tablas del mismo tipo. Por ejemplo: SELECT ... FROM "C:\MyDB\Tabla1.dbf" T1, ':DBDEMOS:Tabla2.db' T2 Nota: SQL admite comillas simples o dobles. T1 y T2 son alias locales para referirse a estas tablas. Por ejemplo si ambas tablas tuvieran un campo llamado Codigo, puede referirse al de la primera tabla calificándolo con el alias de la tabla: T1.Codigo. WHERE ----- Especifica la condición para combinar y/o filtrar los registros de las tablas. Si no especifica una cláusula WHERE, el resultado de combinar dos o más tablas es el producto cartesiano de las mismas, es decir, todos los registros de una tabla se combinan con todos los registros de las otras. Por ejemplo, si T1 tuviera 3 registros y T2 tuviera 2 registros, entonces el resultado tendría 6 registros: T1 T2 --------------------- --------------------------- C11 C12 C13 C14 C21 C22 C23 C24 C25 --------------------- --------------------------- 5 ... ... ... 5 ... ... ... ... 7 ... ... ... 8 ... ... ... ... 8 ... ... ... T1 x T2 --------------------------------------------------- C11 C12 C13 C14 C21 C22 C23 C24 C25 --------------------------------------------------- 5 ... ... ... 5 ... ... ... ... 5 ... ... ... 8 ... ... ... ... 7 ... ... ... 5 ... ... ... ... 7 ... ... ... 8 ... ... ... ... 8 ... ... ... 5 ... ... ... ... 8 ... ... ... 8 ... ... ... ... Usualmente esto no es lo que queremos, sino que sólo queremos los registros cuyos valores de clave coinciden, así que la primer condición de una cláusula WHERE generalmente es una "condición de combinación" como por ejemplo C11 = C21, que debería dar el siguiente resultado: T1 x T2 WHERE C11 = C21 --------------------------------------------------- C11 C12 C13 C14 C21 C22 C23 C24 C25 --------------------------------------------------- 5 ... ... ... 5 ... ... ... ... 8 ... ... ... 8 ... ... ... ... En el ejemplo de clientes y órdenes teníamos tanto una "condición de combinación" como una "condición de filtro": Orders.CustNo = Customer.CustNo AND Country = "US" GROUP BY -------- Especifica la lista de campos sobre los cuáles se agruparán los registros. El agrupamiento consiste en hacer que un grupo de registros se hagan uno. En nuestro ejemplo de clientes y órdenes, sin el agrupamiento tendríamos muchos registros por cada cliente. Por ejemplo el cliente 3053 tendría tres registros: CustNo Company AmountPaid 3053 American SCUBA Supply $ 10.263,75 3053 American SCUBA Supply $ 158.922,65 3053 American SCUBA Supply $ 13.908,00 Cuando en realidad lo que queremos es un solo registro resumiendo los tres: CustNo Company TotalAmount 3053 American SCUBA Supply $ 183,094.40 ¿Cómo realizamos el agrupamiento? Con la cláusula GROUP BY listando todos los campos no agregados (SUM(AmountPaid) es un campo agregado) que están en la cláusula SELECT, por ejemplo: GROUP BY Orders.CustNo, Company Si usa una función agregada (AVG, COUNT, MAX, MIN o SUM) en la cláusula SELECT, entonces se requiere que GROUP BY esté presente (otros motores de bases de datos asumen que todos los registros deben agruparse si usó una función agregada y no especificó GROUP BY). HAVING ------ Especifica una condición del filtro a aplicarse después del agrupamiento y se usa para filtrar registros basándose en los valores de campos agregados (valores que obviamente no están disponibles antes del agrupamiento). Por ejemplo: HAVING SUM(AmountPaid) > 100000 Esta cláusula es opcional. ORDER BY -------- Especifica la lista de campos sobre los cuales se ordenarán los registros. Por ejemplo: ORDER BY TotalAmount DESC, Company; Esto ordenaría los registros en forma descendente por monto total (el más grande primero) y si dos registros tienen el mismo valor para este campo, serán ordenados en forma ascendente por sus nombres. Esta cláusula es opcional. JOINs ----- La condición de combinación se puede especificar en la cláusula WHERE, pero también se puede hacer en la cláusula FROM. Por ejemplo, estas dos sentencias SQL son equivalentes: SELECT Orders.CustNo, Company; FROM Orders, Customer WHERE Orders.CustNo = Customer.CustNo SELECT Orders.CustNo, Company; FROM Orders INNER JOIN Customer ON Orders.CustNo = Customer.CustNo La palabra "INNER" es opcional. La primera se llama habitualmente equi-combinación, mientras que a la segunda forma se la llama combinación interna, pero a pesar de los diferentes nombres producen el mismo resultado. El INNER JOIN descarta los registros en una tabla que no tienen clave correspondiente en la otra. Puede ver esto en el ejemplo que presen- tamos con la cláusula WHERE: El registro con la clave 7 en T1 fue descartado del resultado porque no había 7 en T2. A veces sí queremos estos registros "huérfanos" y esa es la razón por la que hay un OUTER JOIN. Hay tres clases de OUTER JOIN: - LEFT JOIN: Es como INNER JOIN pero también incluye registros en la primera tabla que no tienen pareja en la segunda tabla. - RIGHT JOIN: Es como INNER JOIN pero también incluye registros en la segunda tabla que no tienen pareja en la primera tabla. - FULL JOIN: Una combinación de LEFT JOIN y RIGHT JOIN, es decir, es como INNER JOIN pero incluye registros de ambas tablas que no tienen su correspondiente en la otra. Cuando no se encuentra coincidencia en una tabla, sus campos valen NULL. En el ejemplo de T1 y T2, la siguiente sentencia SELECT T1.*, T2.* FROM T1 LEFT OUTER JOIN T2 ON C11 = C21 arrojaría el siguiente resultado: --------------------------------------------------- C11 C12 C13 C14 C21 C22 C23 C24 C25 --------------------------------------------------- 5 ... ... ... 5 ... ... ... ... 7 ... ... ... NULL NULL NULL NULL NULL 8 ... ... ... 8 ... ... ... ... Debemos advertirles que hemos probado unas pocas consultas usando OUTER JOIN y no parece que funcione (actúa como INNER JOIN). Se pueden combinar dos tablas por más de un campo. Por ejemplo: SELECT t1.*, t2.* FROM t1 JOIN t2 ON t1.c1 = t2.c1 AND t1.c2 = t2.c2 También se pueden combinar dos tablas sobre la coincidencia de un campo y una concatenación de campos: SELECT t1.*, t2.* FROM t1 JOIN t2 ON t1.campo1 = t2.campo1 || t2.campo2 Se puede combinar más de dos tablas. Por ejemplo: SELECT t1.*, t2.*, t3.* FROM (t1 JOIN t2 ON t1.campo1 = t2.campo1) JOIN t3 ON t3.campo1 = t1.campo2 AND t3.campo2 = t1.campo2 SELECT t1.*, t2.*, t3.* FROM ((t1 JOIN t2 ON t1.campo1 = t2.campo1) JOIN t3 ON t3.campo1 = t1.campo2 AND t3.campo2 = t1.campo2) JOIN t4 ON t4.campo1 = t1.campo3 PARAMETROS ---------- En la primer consulta de ejemplo obtuvimos los montos de los clientes de los EE.UU., Add('WHERE Orders.CustNo = Customer.CustNo AND Country = "US"'); pero qué pasaría si quisiéramos ver aquellos de nuestros clientes Canadienses? Podemos hacer dos cosas: concatenar cadenas o declarar un parámetro precediendo su nombre por dos puntos (':') como puede ver abajo: Add('WHERE Orders.CustNo = Customer.CustNo AND Country = :Country'); Antes de abrir la consulta debemos proveer el valor para el parámetro. Por ejemplo: Query1.Params[0].AsString := Edit1.Text; o Query1.ParamByName('Country').AsString := 'Canada'; Esto permite escribir la consulta SQL una vez y luego solamente cambiar el parámetro como se necesite. Debido a que usamos AsString, el valor del parámetro será entrecomillado en la sentencia SQL. EJEMPLOS -------- 1) Obtener las partes en orden decreciente de margen de ganancias neto unitario. SELECT parts.*, (ListPrice - Cost) AS profit FROM "parts.db" parts ORDER BY profit DESC 2) Obtener las partes que necesitan reposición inmediata para cumplir las órdenes. Por cada parte listar su número, descripción cantidad necesaria, costo unitaro, monto total, nombre del proveedor y número de teléfono. Ordenar los resultados por nombre de vendedor y número de parte. SELECT PartNo, Description, (OnOrder - OnHand) As Quantity, Cost, (Quantity * Cost) As Total, VendorName, Phone FROM "parts.db" parts JOIN "vendors.db" vendors ON parts.VendorNo = vendors.VendorNo WHERE OnOrder > OnHand ORDER BY VendorName, PartNo 3) Obtener la parte más vendida. SELECT PartNo, Description, SUM(Qty) As Quantity FROM "parts.db" parts JOIN "items.db" items ON parts.PartNo = items.PartNo GROUP BY PartNo, Description ORDER BY Quantity DESC El primer registro de esta consulta corresponde a la parte más vendida. Con otros motores de bases de datos (como el JET) se puede usar SELECT TOP 1 ... para obtener sólo el primer registro. 4) Listar todos los empleados y sus ventas totales en 1994 y 1995 SELECT employee.EmpNo, LastName, FirstName, SUM(ItemsTotal) AS Sales FROM employee LEFT JOIN orders ON employee.EmpNo = orders.EmpNo WHERE SaleDate BETWEEN "01/01/1994" AND "12/31/1995" GROUP BY employee.EmpNo, LastName, FirstName La cláusula WHERE también pudo haber sido por ejemplo: * WHERE SaleDate >= "01/01/1994" AND SaleDate <= "12/31/1995" * WHERE EXTRACT(YEAR FROM SaleDate) BETWEEN 1994 AND 1995 * WHERE EXTRACT(YEAR FROM SaleDate) IN (1994, 1995) Las fechas siempre se expresan en formato "MM/DD/AAAA" 5) Listar todos los empleados que han tratado con clientes en Fiji SELECT employee.EmpNo, LastName, FirstName FROM (employee JOIN orders ON employee.EmpNo = orders.EmpNo) JOIN customer ON orders.CustNo = customer.CustNo WHERE customer.Country = "Fiji" 6) Por cada empleado, totalizar las ventas a cada uno de sus clientes. SELECT employee.EmpNo, LastName, FirstName, Company, SUM(ItemsTotal) AS Sales FROM (employee JOIN orders ON employee.EmpNo = orders.EmpNo) JOIN customer ON orders.CustNo = customer.CustNo GROUP BY LastName, FirstName, employee.EmpNo, Company 7) Obtener todas las órdenes de un cliente cuyo número debe ser provisto por el usuario: SELECT * FROM orders WHERE CustNo = :Custno El código fuente completo de los ejemplos de este boletín está disponible aquí: http://www.latiumsoftware.com/descarga/p0008.zip ________________________________________________________________________ 4. ENLACES EN INGLES ========= * Top 100 Delphi web sites http://www.sandbrooksoftware.com/TS/index.shtml * All the best from James M Sandbrook - Delphi Programming Source Code. Free components, downloads, articles, examples etc. http://www.sandbrooksoftware.com/DPSC/index.shtml * DelphiLand - Online tutorials for the novice Delphi programmer. All lessons and source code can also be downloaded. http://www.festra.com/eng/index.html EN ESPAÑOL ========== * P.O.D. - Programación Orientada en Delphi Página personal de Alirio Gavidia con tips, enlaces, etc. Lo más interesante es TExpression, una unidad para evaluar expresiones dadas como cadenas en tiempo de ejecución. http://www.gavidia.org/pod ________________________________________________________________________ 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/p0008.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!






