Boletín Pascal #7
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
Boletín Pascal #7 INDICE 1. UNAS PALABRAS DEL EDITOR 2. PROGRAMACION DE BASES DE DATOS EN CODIGO - CREATE STRUCTURE - USE - APPEND (y REPLACE) - GO TOP, GO BOTTOM, SKIP, SKIP n y EOF - REPLACE - DELETE - INSERT - LOCATE - LOCATE...CONTINUE y SET FILTER - SET ORDER TO - SEEK y (SOFTSEEK o SET NEAR) - SET FILTER (RANGOS) 3. EL NOVATO Y EL EXPERTO - Indices y punteros 4. ENLACES ________________________________________________________________________ 1. UNAS PALABRAS DEL EDITOR Quisiéramos aprovechar para agradecer a todos nuestros suscriptores que respondieron el cuestionario que enviáramos un par de semanas atrás, muy especialmente a quienes hicieron comentarios, puesto que TODOS han sido muy útiles. Les pedimos perdón por no responder a todos pero esperamos que comprendan que recibimos una avalancha de emails. El resultado es que definitivamente el formato del boletín seguirá siendo más o menos como es ahora: quincenal, 25K y texto simple. Un poco más conciso quizás. Acerca de los attachments (archivos adjuntos), en el futuro se los enviaremos únicamente a quienes elijan recibirlos (son la gran mayoría, pero parte de la minoría claramente objetó esta idea, así que sentimos que no hay suficiente consenso para enviarles attachments a todos). El nivel del newsletter se mantendrá más o menos igual en promedio, pero algunas veces trataremos de mezclar un poco de temas más y menos avanzados, o trataremos de mezclar explicaciones "avanzadas" con explicaciones "triviales". No va a ser tarea fácil... La escritura calificó entre buena y muy buena, lo que nos conforma bastante, pero trataremos de mejorar en lo que podamos, aunque los regionalismos serán difíciles de evitar. Este boletín no es algo que hacemos para nosotros mismos, sino para ustedes, así que por favor no esperen al próximo cuestionario dentro de tres meses para realizar comentarios, críticas, sugerencias, proponer temas, etc. 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. PROGRAMACION DE BASES DE DATOS EN CODIGO La mayoría de quienes han trabajado en un lenguaje xBase (como dBase, Fox o Clipper) parecen extrañar la posibilidad de hacer todo con código sin tener que meterse con componentes en un formulario y un Inspector de objetos... Bueno, las buenas noticias para todos es que Delphi permite que todo lo que se puede hacer visualmente también se pueda hacer por código. Aquí les proveeremos de algunos ejemplos de programación de bases de datos usando la BDE y en el futuro trataremos también ADO (la mayoría de las cosas son iguales). Para probar los ejemplos creen una nueva aplicación y en la unidad del formulario agreguen DB y DBTables en la cláusula USES. CREATE STRUCTURE ================ Para crear una tabla podemos usar un código como el siguiente: procedure TForm1.Button1Click(Sender: TObject); var Productos: TTable; begin Productos := TTable.Create(nil); try with Productos do begin // Definición de la tabla DatabaseName := ExtractFilePath(Application.ExeName); TableType := ttDBase; TableName := 'Productos.DBF'; with FieldDefs do begin // Definición de los campos with AddFieldDef do begin Name := 'IDProducto'; DataType := ftSmallint; end; with AddFieldDef do begin Name := 'Descripcion'; DataType := ftString; Size := 25; end; with AddFieldDef do begin Name := 'Precio'; DataType := ftFloat; end; end; with IndexDefs do begin // Definición de los índices with AddIndexDef do begin Name := 'PorID'; Fields := 'IDProducto'; end; with AddIndexDef do begin Name := 'PorDescr'; Fields := 'Descripcion'; end; end; CreateTable; // Crea la tabla end; except Productos.Free; raise; end; Productos.Free; end; Primero declaramos el componente TTable y lo creamos sin dueño (pará- metro nil), significando esto que seremos responsables de liberar el objeto. Si en su lugar hubiéramos escrito Productos := TTable.Create(Self); entonces el componente pertenecería a Form1 y el formulario lo liberará cuando él sea liberado (por ejemplo cuando la aplicación termine). Puesto que decidimos liberar "manualmente" el objeto, usamos un bloque TRY para asegurar que llegaremos a hacerlo incluso si ocurre una excepción. Luego realizamos todo el proceso de crear una tabla dentro de un bloque WITH para ahorrarnos escritura... Sin el bloque WITH tendríamos que escribir: Productos.DatabaseName := ExtractFilePath(Application.ExeName); Productos.TableType := ttDBase; Productos.TableName := 'Productos.DBF'; FieldDef := Productos.FieldDefs.AddFieldDef; FieldDef.Name := 'IDProducto'; ... La ventaja de no usar un bloque WITH es que se pueden usar las caracte- rísticas de autocompletar del editor de código y también a veces el código es más claro. Usando WITH se escribe menos y su código es más eficiente cuando la expresión WITH resuelve alguna referencia (por ejemplo "with a.b do" o "with a[i] do", o en algunos WITHs anidados) porque la resolución se realiza una sola vez y el resultado se mantiene en un registro de la UCP (EBX más precisamente) para acceso rápido. Ahora volvamos a las propiedades de la tabla... * DatabaseName es el nombre de la base de datos a la que la tabla pertenece (o pertenecerá). El valor de esta propiedad puede ser un directorio (por ejemplo usamos el directorio donde se encuentra la aplicación) o un alias BDE (por ejemplo DBDEMOS). * TableType define el tipo de la tabla. Normalmente sólo lo establecemos al crear una tabla. En el ejemplo elegimos una tabla dBase. * TableName es el nombre de la tabla. Por ejemplo para tablas dBase y Paradox éste es el nombre del archivo de la tabla (el directorio es determinado por DatabaseName). * FieldDefs es una lista de objetos TFieldDef, cada uno de los cuales representa la definición de un campo. El método AddFieldDef agrega una definición de campo a la lista y devuelve una referencia a él, la que usamos en un bloque WITH para establecer las propiedades de este campo recientemente agregado (por ejemplo: Name, DataType, Size, Precision, Required, etc.). * IndexDefs es una lista de objetos TIndexDef, cada uno de los cuales representa la definición de un índice. El método AddIndexDef agrega una definición de índice a la lista y devuelve una referencia a él, la que usamos en un bloque WITH para establecer las propiedades de este índice recientemente agregado (por ejemplo: Name, Fields, etc.). Si quiere un índice por más de un campo, todo lo que tiene que hacer es separar los campos por punto y coma (";"): Fields := 'Campo1;Campo2;Campo3'; Una vez que hemos establecido todas estas propiedades (siendo IndexDefs opcional) procedemos a crear la tabla invocando el método CreateTable. Después de esto deberíamos tener un archivo llamado Productos.DBF en el directorio de la aplicación. USE === Para abrir y cerrar una tabla podemos usar la propiedad Active o los métodos Open y Close: procedure TForm1.Button2Click(Sender: TObject); var Productos: TTable; begin Productos := TTable.Create(nil); try with Productos do begin DatabaseName := ExtractFilePath(Application.ExeName); TableName := 'Productos.DBF'; Open; // Active := True; // Trabajar con la tabla aquí Close; // Active := False; end; except Productos.Free; raise; end; Productos.Free; end; APPEND (y REPLACE) ================== Para agregar nuevos registros al final de la tabla podemos agregar algunas líneas entre las llamadas a los métodos Open y Close de arriba: // APPEND BLANK // REPLACE IDProducto WITH 137, Descripcion WITH 'Gaseosas', ; // Precio WITH 0.35 Append; FieldByName('IDProducto').AsInteger := 137; FieldByName('Descripcion').AsString := 'Gaseosas'; FieldByName('Precio').AsFloat := 0.35; Post; Ahora agreguemos algunos registros más para usarlos después en los ejemplos: Append; FindField('IDProducto').AsInteger := 243; FindField('Descripcion').AsString := 'Cigarros'; FindField('Precio').AsFloat := 1.15; Post; Append; Productos['IDProducto'] := 395; Productos['Descripcion'] := 'Galletas'; Productos['Precio'] := 1.35; Post; Append; FieldValues['IDProducto'] := 45; FieldValues['Descripcion'] := 'Caramelos'; FieldValues['Precio'] := 0.05; Post; Append agrega un registro en blanco (no exactamente en blanco porque los campos pueden tener valores predeterminados) pero la operación no se completa hasta que llamamos al método Post para guardar los cambios. Como se puede ver en los ejemplos, hay muchas formas de referenciar campos y sus valores: * FieldByName y FindField ambos devuelven una referencia a un objeto TField. La diferencia es que si el nombre del campo pasado como parámetro no se encuentra, FieldByName genera un excepción mientras que FindField devuelve nil. * FieldValues es la propiedad predeterminada de la clase TTable (esto significa que por ejemplo "Tabla1.FieldValues['Campo1']" es lo mismo que sólo "Tabla1['Campo1']") y representa un arreglo de variants con los valores de los campos. GO TOP, GO BOTTOM, SKIP, SKIP n y EOF ===================================== Ahora que tenemos cuatro productos IDProducto Descripcion Precio 137 Gaseosas 0.35 243 Cigarros 1.15 395 Galletas 1.35 45 Caramelos 0.05 podemos desplazarnos por ellos: First; // Va al primer registro (GO TOP) ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas Last; // Va al último registro (GO BOTTOM) ShowMessage(FieldValues['Descripcion']); // Caramelos Next; // Va al siguiente registro (SKIP) if Eof then ShowMessage('EOF'); ShowMessage(Productos['Descripcion']); // Caramelos Prior; // Va al registro anterior (SKIP -1) ShowMessage(FieldValues['Descripcion']); // Galletas MoveBy(-2); // Va dos registros hacia arriba (SKIP -2) ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas En los dialectos xBase, cuando uno está en fin de archivo (EOF), está posicionado en una especie de registro en blanco que sigue al último registro de la tabla, y los campos valen cero, falso, espacios o la fecha nula, dependiendo de los tipos de los campos. Una vez alcanzado el fin de archivo se puede volver al último registro con SKIP -1. En Visual Basic, cuando uno está en fin de archivo no hay registro activo (cualquier intento de acceder a los valores de los campos genera un error), pero puede volver al último registro con MovePrevious. En Delphi, EOF es una señal indicando que se intentó un desplazamiento más allá del fin de archivo, pero aún está posicionado en el último registro (a menos que la tabla esté vacía, y entonces no habrá registro activo). REPLACE ======= Para reemplazar los valores de uno o más campos de un registro usamos Edit para preparar el registro para cambios, luego actualizamos los campos y finalmente guardamos los cambios con Post: // REPLACE Descripcion WITH 'Gaseosas Diet', Precio WITH 0.40 Edit; FieldByName('Descripcion').AsString := 'Gaseosas Diet'; FieldByName('Precio').AsFloat := 0.40; Post; DELETE ====== ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas Diet Delete; ShowMessage(FieldByName('Descripcion').AsString); // Cigarros En los lenguajes xBase, justo después de borrar un registro, éste aún es el registro activo y puede ser recuperado (con RECALL). Puede moverse al siguiente registro con SKIP. En Visual Basic, después de llamar al método Delete de un Recordset no hay registro activo (cualquier intento de acceder a los valores de los campos genera un error), pero puede pasar al siguiente registro con MoveNext. Los registros borrados no son recuperables (al menos no lo son usando los Recordsets de VB). En Delphi, el método Delete borra el registro activo y el siguiente registro se hace activo. Los registros borrados no son recuperables (al menos no lo son usando los Datasets de Delphi). INSERT ====== Last; Insert; // Puede agregar antes del último registro o al final FieldByName('IDProducto').AsInteger := 137; FieldByName('Descripcion').AsString := 'Gaseosas'; FieldByName('Precio').AsFloat := 0.35; Post; Insert es casi como Append. Dependiendo del tipo de base de datos el nuevo registro puede ser agregado al final de la tabla o ser físicamente insertado antes del registro actual. Para tablas DBFs, Insert es lo mismo que Append. LOCATE ====== Locate busca el primer registro con los valores de campo que indique. Por ejemplo: // LOCATE FOR Descripcion = 'Caramelos' Locate('Descripcion', 'Caramelos', [loCaseInsensitive]); ShowMessage(FieldByName('Descripcion').AsString); // Caramelos // LOCATE FOR IDProducto = 243 .AND. Descripcion = 'Cigarros' Locate('IDProducto;Descripcion', VarArrayOf([243,'Cigarros']), [loCaseInsensitive]); ShowMessage(FieldByName('Descripcion').AsString); // Cigarros Puede buscar por claves parciales (sólo afecta a los campos de texto): Locate('Descripcion', 'GALL', [loCaseInsensitive, loPartialKey]); ShowMessage(FieldByName('Descripcion').AsString); // Galletas Y también puede realizar búsquedas sensibles a mayúsculas/minúsculas: if not Locate('Descripcion', 'CARAMELOS', []) then ShowMessage('CARAMELOS no encontrado'); ShowMessage(FieldByName('Descripcion').AsString); // Cigarros Locate devuelve True si encontró un registro y False en caso contrario. Después de una búsqueda sin éxito, en los lenguajes xBase el registro activo es EOF, en Visual Basic no hay registro activo, y en Delphi el registro activo no cambia. LOCATE...CONTINUE y SET FILTER ============================== Si necesitáramos encontrar el siguiente o los siguientes registros que verifican el criterio, en xBase podemos hacer lo siguiente: LOCATE FOR <condición> WHILE .NOT. EOF() * Procesar los registros aquí CONTINUE ENDDO Esto también puede lograrse así: SET FILTER TO <condición> GO TOP WHILE .NOT. EOF() * Procesar los registros aquí SKIP ENDDO SET FILTER TO En los Datasets de Delphi no tenemos un equivalente para CONTINUE, así que tenemos que usar filtros. Hay dos maneras de establecer un filtro: 1) Usando la propiedad Filter (y la propiedad FilterOptions) FilterOptions := <opciones>; Filter := <condición>; // Establece el filtro if FindFirst then repeat // Procesar los registros aquí until not FindNext; Filter := ''; // Remueve el filtro 2) Usando un evento OnFilterRecord OnFilterRecord := <manejador de evento>; // Establece el filtro if FindFirst then repeat // Procesar los registros aquí until not FindNext; OnFilterRecord := nil; // Remueve el filtro Veremos ambas maneras en el siguiente ejemplo: type TForm1 = class(TForm) ... private { Private declarations } procedure Filtro(DataSet: TDataSet; var Accept: Boolean); public { Public declarations } end; ... procedure TForm1.Filtro(DataSet: TDataSet; var Accept: Boolean); begin Accept := Pos('K', DataSet['Company']) <> 0; end; procedure TForm1.Button3Click(Sender: TObject); var Clientes: TTable; begin Clientes := TTable.Create(nil); try with Clientes do begin DatabaseName := 'DBDEMOS'; TableName := 'Customer.DB'; Active := True; FilterOptions := [foCaseInsensitive]; Filter := 'Country = ' + QuotedStr(InputBox( 'Filtro', 'Ingrese el país a filtrar', 'bAhAmAs')); if FindFirst then repeat ShowMessage(FieldValues['Company'] + ' (' + FieldValues['Country'] + ')'); until not FindNext; Filter := ''; // Remueve el filtro ShowMessage('Ahora con el evento OnFilterRecord'); OnFilterRecord := Filtro; if FindFirst then repeat ShowMessage(FieldValues['Company']); until not FindNext; OnFilterRecord := nil; // Remueve el filtro Active := False; end; except Clientes.Free; raise; end; Clientes.Free; end; FindFirst, FindNext, FindPrior y FindLast son como First, Next, Prior y Last, pero sólo se mueven por los registros que cumplen la condición del filtro y devuelven un valor (True si se encontró un registro y False en caso contrario). Filtrando vía la propiedad Filter es más simple porque se puede hacer in-situ y es relativamente más poderoso en el sentido que se le puede solicitar a usuarios avanzados toda la condición: Clientes.Filter := InputBox('Filtro', 'Ingrese la condición', 'Country = ''US'' OR Country = ''Bahamas'''); Usando el evento OnFilterRecord implica algunas líneas más de código, pero puede resultar más rápido puesto que la BDE no tiene que inter- pretar la condición de filtro, y es relativamente más poderoso en el sentido que uno no está limitado a una "simple" condición de filtro y en el manejador de evento (el procedimiento que referencia OnFilterRecord) uno puede construir su lógica propia para determinar si el registro debería ser filtrado o no (puede llamar a funciones, usar IFs, buscar registros en otras tablas, etc.). Finalmente hay que establecer el parámetro Accept en True (el registro cumple con el criterio) o False (el registro debe ser excluido). SET ORDER TO ============ Para determinar el índice activo todo lo que tenemos que hacer es asignar a la propiedad IndexName el nombre del índice. Por ejemplo: procedure TForm1.Button4Click(Sender: TObject); var Productos: TTable; begin Productos := TTable.Create(nil); try with Productos do begin DatabaseName := ExtractFilePath(Application.ExeName); TableName := 'Productos.DBF'; Open; // Active := True; // Ningún índice marcado como primario // ==> Orden natural es el predeterminado ShowMessage(FieldByName('Descripcion').AsString); // Cigarros IndexName := 'PorID'; // SET ORDER TO PorID First; ShowMessage(FieldValues['Descripcion']); // Caramelos IndexName := 'PorDescr'; // SET ORDER TO PorDescr First; ShowMessage(FieldValues['Descripcion']); // Galletas IndexName := ''; // SET ORDER TO First; ShowMessage(FieldValues['Descripcion']); // Cigarros Close; // Active := False; end; except Productos.Free; raise; end; Productos.Free; end; SEEK y (SOFTSEEK o SET NEAR) ============================ Hay una forma corta y una larga de realizar un "seek" (búsqueda directa por un índice). Esta es la forma larga: // SEEK Valor1+Valor2 // IF FOUND() // ... SetKey; // Limpia la clave FieldValues['Campo1'] := Valor1; FieldValues['Campo2'] := Valor2; if GotoKey then ... // SEEK Valor1+Valor3 // IF FOUND() // ... EditKey; // Edita la clave FieldValues['Campo2'] := Valor3; if GotoKey then ... Y estas son las versiones cortas de los dos ejemplos anteriores: if FindKey([Valor1, Valor2]) then ... if FindKey([Valor1, Valor3]) then ... La forma larga tiene mejor rendimiento, pero la diferencia es sólo marginal, por lo que preferimos la forma corta por ser más simple. GotoKey y FindKey devuelven True si encuentran una coincidencia exacta y False en caso contrario. Puede buscar por claves parciales con los procedimientos GotoNearest y FindNearest, que son los equivalentes a a SOFTSEEK en Clipper o usar SET NEAR ON con SEEK en FoxPro. Aquí hay algunos ejemplos: IndexName := 'PorID'; SetKey; FieldByName('IDProducto').AsInteger := 137; if GotoKey then ShowMessage(FieldValues['Descripcion']); // Gaseosas if FindKey([45]) then ShowMessage(FieldValues['Descripcion']); // Caramelos IndexName := 'PorDescr'; SetKey; FieldByName('Descripcion').AsString := 'Cigarros'; if GotoKey then ShowMessage(FieldValues['Descripcion']); // Cigarros SetKey; FieldByName('Descripcion').AsString := 'Caramelos'; if GotoKey then ShowMessage(FieldValues['Descripcion']) else ShowMessage('Caramelos no encontrado'); SetKey; FieldByName('Descripcion').AsString := 'Ga'; GotoNearest; if Copy(FieldValues['Descripcion'],1,2) = 'Ga' then ShowMessage(FieldValues['Descripcion']); // Galletas FindNearest(['Car']); if Copy(FieldValues['Descripcion'],1,3) = 'Car' then ShowMessage(FieldValues['Descripcion']); // Caramelos SET FILTER (RANGOS) =================== Existe una tercera forma de hacer un filtro: estableciendo un rango de claves en el índice activo. Por ejemplo: // SET ORDER TO PorID // SET FILTER TO IDProducto >= 100 .AND. IDProducto <= 300 // GO BOTTOM // ? Descripcion // SEEK 45 // IF FOUND() // ? Descripcion // ELSE // ? 'Caramelos no encontrado' // ENDIF // SET FILTER TO IndexName := 'PorID'; SetRange([100],[300]); ApplyRange; Last; ShowMessage(FieldValues['Descripcion']); // Galletas if FindKey([45]) then ShowMessage(FieldValues['Descripcion']) else ShowMessage('Caramelos no encontrado'); CancelRange; Esto es todo por ahora. En el próximo número seguiremos explorando las formas de hacer en Delphi las cosas que se pueden hacer en los lenguajes xBase. El código fuente completo de los ejemplos de este boletín está disponible en esta dirección: http://www.latiumsoftware.com/descarga/p0007.zip A propósito, los ejemplos del último boletín estuvieron disponibles un poco tarde. Pedimos disculpas por los inconvenientes. ________________________________________________________________________ 3. EL NOVATO Y EL EXPERTO - Indices y punteros Aquí presentamos un ejemplo de una función que devuelve la posición de la primera ocurrencia de un caracter dentro de una cadena. NOVATO: function Scan(s: string; char c): integer; var i: integer: begin Result := 0; for i := 1 to Length(s) do begin if s[i] = c then begin Result := i; break; end; end; end; EXPERTO: function Scan(const s: string; char c): integer; var i: integer: p: pchar; begin Result := 0; p := PChar(s); for i := 1 to Length(s) do begin if p^ = c then begin Result := i; break; end; inc(p); end; end; EXPLICACION: PChar significa "puntero a caracter". La sentencia "p := PChar(s);" hace que p apunte al primer caracter en la cadena s, así que decir s[1] es lo mismo que decir p^ (que significa "el caracter apuntado por p"). La sentencia "inc(p);" incrementa el puntero (o sea, le suma uno), haciendo que apunte al siguiente caracter, de modo que la primera vez que se ejecute p apuntará a s[2] (entonces p^ = s[2]). La siguiente vez que p se incremente, apuntará a s[3] (así que p^ = s[3]), y así sucesi- vamente, de modo que tenemos el mismo efecto que si usáramos índices. La diferencia es que con punteros la función es más veloz. Otro cambio que hemos hecho fue agregar "const" en la declaración del parámetro de cadena (s). Esto le dice al compilador que la cadena debe ser pasada por referencia (es más rápido), pero que no intentaremos modificarla. ARTIMETICA DE PUNTEROS: function Scan(const s: string; char c): integer; var p, q: pchar; begin p := PChar(s); q := StrScan(p, c); if q := nil then Result := 0 else Result := q - p + 1; end; Delphi provee una función llamada StrScan que toma un pchar en vez de una cadena como parámetro y devuelve un puntero a la primera ocurrencia del caracter, en vez de su posición, así que usamos aritmética de punteros para obtener esta posición. Por ejemplo, supongamos que llamamos "Scan('OOOXOOO', 'X');" 1) Después de "p := PChar(s);", p es la dirección de memoria del primer caractrer de la cadena s. Una dirección de memoria es un número (como un índice en el arreglo de la memoria), por ejemplo $1000. 2) Después de "q := StrScan(p, c);" q apunta a la primera ocurrencia del caracter c ('X') en la cadena, así que en el ejemplo sería $1003. 3) Para obtener la posición usamos la fórmula "q - p + 1". En nuestro ejemplo sería : $1003 - $1000 + 1 = 3 + 1 = 4 (el caracter encontrado es el cuarto de la cadena). +---+---+---+---+---+- | O | O | O | X | O | +---+---+---+---+---+- 1 2 3 4 5 | | | | | p = $1000 ----+ | | | | $1001 --------+ | | | $1002 ------------+ | | q = $1003 ----------------+ | $1004 --------------------+ ________________________________________________________________________ 4. ENLACES EN INGLES ========= * Aplicaciones Delphi con código fuente http://sunsite.informatik.rwth-aachen.de/delphi/ * Enlaces Delphi en el Proyecto del Directorio Abierto (Inglés) http://dmoz.org/Computers/Programming/Languages/Delphi/ * Delphi Links at Cetus-Links.org http://www.cetus-links.org/oo_delphi.html * Oberon Microsystems - Aquí encontrarán BlackBox, una herramienta de desarrollo de software basada en componentes que usa Oberon, un sucesor del lenguaje Pascal inventado por el mismísimo Prof. Niklaus Wirth. Hay una versión educativa disponible para descargar (~6 Mb). Héchele un vistazo y después cuéntenos que le pareció. http://www.oberon.ch EN ESPAÑOL ========== +======================================================================+ | PSP S.A. | | Este Boletín Pascal hoy no existiría de no ser por la inspiración | | que he obtenido del Dr. Marcelo Perazolo, a través de su boletín | | "Novedades Empresarias y Profesionales" que LLEVA YA 50 EJEMPLARES | | Y CON SUS 110.000 SUSCRIPTORES ES EL BOLETIN DE MAYOR TIRADA EN EL | | MUNDO. El Dr. Perazolo ha tenido la gentileza de mencionarnos en su | | newsletter, así que cumplimos mencionándolo en el nuestro, y apro- | | vechamos para felicitarlo desde aquí por su merecido logro. Quienes | | estén interesados en recibir esta publicación (se las recomiendo) | | deben solicitarla escribiendo a <empresa@psp-sa.com> | | http://www.psp-sa.com | +======================================================================+ * Numerosas aplicaciones con código fuente y comentarios en español http://www.galeon.com/roberdi/PagDelphi-Download.htm * Enlaces Delphi en el Proyecto del Directorio Abierto (Español) Podrán ver que soy el editor de esta categoría. Si saben de más sitios que deberían estar aquí por favor sugiéranlos. No pueden ser sitios en construcción (a menos que ya tengan bastante material), sitios con una sola página (que explican qué es Delphi, donde conseguirlo y luego los mandan con enlaces a sitios en inglés), sitios que básicamente constan de enlaces y no tienen material propio, sitios que el poco material que tienen ya está en sitios más grandes, cursos y tutoriales (hay otra categoría para eso), ni empresas que venden Delphi o servicios (de nuevo hay otra categoría para eso). Verán que sí hay sitios de una sola página, porque sí aportan algo a la comunidad Delphi. No obstante ello, les agradeceré que me envíen todos los links que encuentren para que yo evalúe si cumplen con las reglas del Directorio Abierto o no (y también pueden opinar al respecto de sitios que piensan que no deberían estar allí). Desde ya muchas gracias por su colaboración. http://dmoz.org/World/Espa%f1ol/Computadoras/Programaci%f3n/Lenguajes/ Delphi/ * Enlaces Delphi de Latium Software Para aquellos que hace mucho no visitan nuestro sito web les comento que hemos actualizado la página de enlaces en español y hoy por hoy es una de las más completas que hay. http://www.latiumsoftware.com/es/links.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/p0007.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!






