Boletín Pascal #14
Los ejemplos completos de código fuente de este número están disponibles para descargar.
![]() |
![]() |
Boletín Pascal #14 - 13-ENE-2001 INDICE 1. UNAS PALABRAS DEL EDITOR 2. KYLIX, ¿PARA CUANDO??? 3. DIBUJANDO CELDAS EN UN DBGRID 4. GESTION DE ERRORES EN DELPHI 5 (I) - EXCEPCIONES - ¿DONDE OCURRIO EL ERROR? - UNA PEQUEÑA SIMPLIFICACION 5. OBTENIENDO EL NOMBRE DE USUARIO DE UNA SESION DE WINDOWS 6. OBTENIENDO EL NOMBRE DE RED DE UNA MAQUINA 7. OBTENIENDO LA LETRA DE LA UNIDAD DE CD-ROM 8. INVOCANDO EL PROGRAMA DE CORREO PREDETERMINADO PARA ENVIAR UN EMAIL 9. ENLACES ________________________________________________________________________ 1. UNAS PALABRAS DEL EDITOR Esta publicación puede crecer y mejorar gracias a la retroalimentación que reciba de los suscriptores, de modo que, a mayor cantidad de ellos, mejor será la calidad de este boletín, y más valioso será para todos nosotros, de modo que les ruego que traten de participar activamente en este emprendimiento y que divulguen la existencia de este boletín entre sus amigos, conocidos, compañeros y colegas que crea que puedan estar interesados en esta publicación. 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, ¿PARA CUANDO??? Delphi para Linux se espera para el primer trimestre de este año, y aparentemente será lanzado simultáneamente junto con Delphi 6 para Windows. Por lo menos, como "subproducto" de la demora Kylix vendrá con soporte para el kernel 2.4 de Linux. La versión beta que he mencionado en números anteriores de este boletín fue entregada a las primeras personas que se unieron al beta program, no es freeware ni shareware, y no puede ser distribuida, así que no está disponible para descargar. Me temo que tendrán que esperar hasta la fecha del lanzamiento de Kylix. Es muy probable que haya una versión de evaluación como las hay para otros productos de Borland. La VCL todavía estará disponible en Delphi 6 para Windows, pensada sólo para desarrollo exclusivo de aplicaciones Windows, pero tanto Kylix como Delphi 6 para Windows vendrán con CLX (se pronuncia "clics"), que es como una VCL portable que esconderá detalles específicos de la implemen- tación dependientes del sistema operativo. CLX usará QT, una conocida biblioteca GUI multi-plataforma, permitiendo que las aplicaciones corran nativamente tanto en Windows como en Linux con sólo una recompilación. Las aplicaciones que usen la VCL sin hacer llamadas a la API de Windows podrán convertirse fácilmente para que usen CLX. CLX será lanzado bajo una licencia dual. Los desarrolladores podrán adquirir una licencia comercial o en su defecto distribuir sus aplica- ciones como GPL. Otra particularidad de CLX es que será utilizable desde cualquier entorno, no requiriéndose la compra de Kylix (se proveerán algunas herramientas de línea de órdenes a tal efecto). La BDE (Borland Database Engine) no estará disponible para Linux, al menos inicialmente, pero sí estará Midas. Kylix traerá soporte para MySql e Interbase, y probablemente otros fabricantes de motores de bases de datos provean datasets para acceder a sus bases de datos para cuando Kylix esté en el mercado. Kylix usará GNOME y KDE, pero al principio no proveerá soporte completo para ninguno de los dos. Se espera que mejorará su soporte para ambos escritorios en una futura versión. ________________________________________________________________________ 3. DIBUJANDO CELDAS EN UN DBGRID Puede dibujar los contenidos de algunas celdas en un DBGrid asignando un procedimiento para manejar el evento OnDrawColumnCell. Cuando DefaultDrawing es True, la DBGrid dibuja la celda antes de llamar a su manejador de evento OnDrawColumnCell. Si intenta dibujar "a mano" todas o muchas de las celdas, debería establecer DefaultDrawing en False para evitar dibujar las celdas dos veces (una vez por la DBGrid y luego otra por su procedimiento), y luego deberá dibujar todas las celdas usted mismo. Aquí va un ejemplo de cómo hacerlo: procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var X, Y, Index, RecNo: integer; DataSet: TDataset; Field: TField; CellText: string; begin with DBGrid1.Canvas do begin ... Lo primero que hace el procedimiento es determinar el color de fondo comprobando si la celda tiene el foco, está seleccionada o es una columna fija. Si es una celda "normal", utilicé el número de registro para mostrar las filas pares y las impares con diferentes colores de fondo. ... // Determinar el color de fondo(y de la fuente) with Brush do begin if gdFocused in State then begin Color := clHighlight; Font.Color := clHighlightText; end else if gdSelected in State then begin Color := clHighlight; Font.Color := clHighlightText; end else if gdFixed in State then begin Color := DBGrid1.FixedColor; end else begin if DataCol = 0 then Color := $AACCFF // Color especial para la primer columna else begin // Determinar el número de la fila if DBGrid1.Datasource <> nil then begin DataSet := DBGrid1.Datasource.DataSet; if DataSet <> nil then begin RecNo := DataSet.RecNo; if RecNo = -1 then begin RecNo := DataSet.RecordCount + 1; if RecNo = 0 then RecNo := 1; end; end else RecNo := 1; end else RecNo := 1; if (RecNo And 1) = 0 then Color := $FFFFEE // Color de fondo para filas impares else Color := $EEFFFF; // Color de fondo para filas pares end; end; end; ... Luego compruebo se la columna corresponde a un campo, y en tal caso simplemente llamo a DefaultDrawColumnCell para que este método dibuje la celda usando el color de brocha (brush) para el fondo y el las propiedades de la fuente para el texto que establecí antes. Puede ver que en el caso del campo ItemsTotal (usé la tabla Orders.DB que viene con la BDE), si su valor es mayor que 10.000 lo muestro en color rojo y en negrita. ... Field := Column.Field; if Field <> nil then begin // Hacer una provisión especial para un campo if Field.FieldName = 'ItemsTotal' then if Field.AsCurrency > 10000 then with Font do begin Color := $FF; Style := Style + [fsBold]; end; // Dibujar la celda por el procedimiento predeterminado DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State); end ... Si la columna no corresponde a un campo, entonces primero relleno la celda con el color de fondo y luego compruebo si es una columna de texto o gráficos. En el primer caso, proveo el texto, luego determino la posición donde debería dibujarlo dentro de la celda de acuerdo con la alineación de la columna, y finalmente lo dibujo. ... else begin // Llenar la celda con el color de fondo FillRect(Rect); if DataCol = 7 then begin // Debería proveer el texto para las columnas propias... CellText := 'Custom Text'; // Determinar la posición del texto case Column.Alignment of taRightJustify: X := Rect.Right - TextWidth(CellText) - 2; taCenter: X := (Rect.Right - Rect.Left - TextWidth(CellText)) div 2 + Rect.Left; else // taLeftJustify: X := Rect.Left + 2; end; // Dibujar el texto TextOut(X, Rect.Top + 2, CellText); end ... Algo similar sucede con el gráfico. ... else if DataCol = 8 then begin // ... o por ejemplo dibujar un gráfico // Determina el gráfico a dibujar if Table1.FieldByName('ItemsTotal').AsCurrency >= 10000 then Index := 0 else Index := 1; // Determina la posición del gráfico dentro de la celda case Column.Alignment of taRightJustify: X := Rect.Right - 2 - 16; taCenter: X := (Rect.Right - Rect.Left - 16) div 2 + Rect.Left; else // taLeftJustify: X := Rect.Left + 2; end; Y := (Rect.Bottom - Rect.Top - 16) div 2 + Rect.Top + 1; // Lo dibuja ImageList1.Draw(DBGrid1.Canvas, X, Y, Index); end; end; if gdFocused in State then // ¿La celda tiene el foco? DBGrid1.Canvas.DrawFocusRect(Rect); // Rectángulo de foco end; end; En la próxima edición mostraré como poner una casilla de verificación en una columna para un campo lógico. ________________________________________________________________________ 4. GESTION DE ERRORES EN DELPHI 5 (I) El propósito de este artículo es mostrarles como capturar los errores que pueden ocurrir en una aplicación para mostrar sus mensajes corres- pondientes, y puesto que los usuarios nunca los recuerdan, les mostraré como pueden guardarlos en una bitácora o registro de errores. También, con la ayuda de un poco de código en ensamblador veremos como determinar donde ocurrió el error. Deseo pedir disculpas por incluir lenguaje ensamblador, pero quería proveerles de una unidad útil que puedan usar en sus proyectos. Aunque voy a tratar de brindar algunas explicaciones, el funcionamiento interno de la unidad que estoy introduciendo es difícil de entender, pero no necesitan hacerlo para poder usar esta unidad en sus proyectos. Es suficiente con tener algún conocimiento sobre excepciones y aprender a grandes rasgos lo que hacen los procedimientos de la unidad y que iré describiendo en este artículo. EXCEPCIONES =========== Muchos errores que ocurren en una aplicación se reportan mediante el mecanismo de excepciones. Puede atrapar las excepciones en bloque try..except..end, como se muestra aquí: uses ErrHndlr; procedure TForm1.Button1Click(Sender: TObject); var StringList: TStringList; begin StringList := nil; try StringList := TStringList.Create; StringList.LoadFromFile('project1.dpe'); ShowMessage(StringList.Text); except on e: exception do begin ShowErrorBox(e.Message); SaveError(e.Message) end; end; StringList.Free; end; Cuando ocurre una excepción dentro de un bloque try, el resto de las sentencias dentro del bloque se omiten y el flujo de ejecución saltará a la parte except que de otro modo (si no ocurre ninguna excepción) sería omitida. En el ejemplo de arriba, el método LoadFromFile alzará una excepción cuando intente abrir un archivo que no existe, y conse- cuentemente el procedimiento ShowMessage no será llamado y el programa continuará ejecutándose en el bloque except donde mostramos un error y lo guardamos en el registro de errores llamando los procedimientos ShowErrorBox y SaveError respectivamente. Podemos definir esos proce- dimientos en una unidad separada como sigue: unit ErrHndlr; interface uses SysUtils; procedure ShowErrorBox(const ErrorMssg: string); procedure SaveError(ErrorMssg: string); implementation uses Forms, Classes, Windows; procedure ShowErrorBox(const ErrorMssg: string); begin Application.MessageBox(PChar(ErrorMssg), PChar(Application.Title), MB_ICONERROR); end; procedure SaveError(const ErrMessage: string); overload; var ErrorsLog: TFileStream; LogFile, ErrorMssg: String; begin ErrorsLog := nil; try // Determina el nombre del archivo de registro de errores LogFile := ChangeFileExt(Application.ExeName, '.err'); ErrorMssg := ErrMessage; // Abre el archivo de registro if FileExists(LogFile) then begin ErrorsLog := TFileStream.Create(LogFile, fmOpenReadWrite + fmShareExclusive); // Ir al final del archivo ErrorsLog.Position := ErrorsLog.Size; end else // No existe: crearlo. ErrorsLog := TFileStream.Create(LogFile, fmCreate); // Antepone al mensaje de error la fecha y hora actual, y // agrega un par CrLf (Retorno de Carro + Alimentación de línea) // al final ErrorMssg := FormatDateTime('ddd dd-mmm-yyyy hh:nn:ss', Now) + ' - ' + ErrorMssg + #13#10; // Escribe en el archivo ErrorsLog.Write(Pointer(ErrorMssg)^, Length(ErrorMssg)); except end; // Cierra la corriente y libera el objeto de la memoria ErrorsLog.Free; end; end. El código es simple. SaveError toma la cadena pasada como parámetro, por ejemplo 'No se puede abrir el archivo project1.dpe', y la "formatea" anteponiéndole la fecha y hora actual, por ejemplo así: Lun 08-Ene-2001 15:35:24 - No se puede abrir el archivo project1.dpe y finalmente agrega esa línea al registro de errores. NOTA: Recomiendo cambiar el código que determina el nombre del archivo de registro porque en sistemas Windows NT o Windows 2000 puede que el usuario no tenga permisos de escritura en el directorio donde está la aplicación. Su aplicación debería leer este valor de un archivo INI o del Registro de Windows. ¿DONDE OCURRIO EL ERROR? ======================== Examinando el registro de errores podemos determinar que ocurrió un error, de que error se trata, y cuando sucedió, pero hasta ahora no sabemos donde. Si tuviéramos la dirección donde ocurrió el problema podríamos entonces utilizar AVFinder. Para aquellos que no han oído hablar de él, AVFinder es un útil programa freeware que nos ayuda a localizar el procedimiento donde ocurrió una Access Violation (AV) en un ejecutable Delphi. Se introduce la dirección que aparece en el cuadro de diálogo de la AV y AVFinder dirá a que módulo, procedimiento y número de línea pertenece. Se puede descargar de esta dirección: http://www.planet-express.com/downloads/ Para que AVFinder funcione necesita que el enlazador (linker) genere un mapa detallado: Project / Options... / Linker / Map file / Detailed. También debería compilar su proyecto sin optimizaciones para que AVFinder reporte la localización de la AV (o el error) correctamente: Project / Options... / Compiler / Code generation / Optimization (desmarcar). Ahora, volviendo al código, sobrecargué SaveError con una nueva versión que acepta una dirección como parámetro extra y la incluye (si fue provista) al construir la línea de error. procedure SaveError(const ErrMessage: string; Address: Pointer); overload; ... ErrorMssg := FormatDateTime('ddd dd-mmm-yyyy hh:nn:ss', Now) + ' - ' + ErrorMssg; if Address <> nil then ErrorMssg := ErrorMssg + ' @ ' + IntToHex(Integer(Address), 1); ErrorMssg := ErrorMssg + #13#10; ... Luego, si modificamos el código de gestión de la excepción para llamar a SaveError pasándole la dirección del procedimiento actual... procedure TForm1.Button1Click(Sender: TObject); ... try ... except on e: exception do begin ShowErrorBox(e.Message); SaveError(e.Message, @TForm1.Button1Click) end; end; ... ...la línea guardada en el registro de errores se vería así: Lun 08-Ene-2001 15:36:50 - No se puede abrir el archivo project1.dpe @ 447484 Está bien, pero ahora nuestro código no es "genérico", algo que podamos copiar y pegar sin modificaciones. Más aún, si por ejemplo cambiáramos el nombre del formulario o del botón, tendríamos que cambiar manualmente el nombre del procedimiento de evento en la llamada a SaveError. Sería bueno si pudiéramos determinar la dirección del procedimiento actual de modo más transparente. Para este propósito escribí una función llamada GetEIP que simplemente devuelve su dirección de retorno, es decir el punto del procedimiento llamador donde el programa continuará su ejecución cuando la función regrese. {$IFOPT W+} {$DEFINE STACKFRAMES_ON} {$W-} {$ENDIF} function GetEIP: Pointer; asm mov eax, [esp]; // Result := ESP^; // Dirección de retorno end; {$IFDEF STACKFRAMES_ON} {$W+} {$UNDEF STACKFRAMES_ON} {$ENDIF} No puedo enseñarles ensamblador, pero trataré de explicar un poco lo que hice aquí. La CPU tiene como una especie de "variables internas" que se llaman "registros", y que tienen nombres como por ejemplo EAX, EBX, ECX, EDX, ESI, EDI, EBP y ESP entre otros. Las funciones que devuelven un valor de 32 bits (como por ejemplo un puntero) deben hacerlo colocando el valor de retorno en el registro EAX antes de volver al llamador. ESP es un registro de propósito especial que se usa para controlar la pila. Cuando una función no tiene "stack frame" o "marco de pila" (forzamos eso con la directiva de compilación $W-), inmediatamente después de entrar a la función, ESP apunta a su dirección de retorno, y en ensam- blador obtenemos el valor al que un registro apunta encerrando el registro entre corchetes ([ESP] equivale a ESP^ en Delphi). Ahora podemos usar un código como el siguiente, que es más "genérico" (podemos copiarlo y pegarlo sin tener que modificarlo): procedure TForm1.Button1Click(Sender: TObject); ... try ... except on e: exception do begin ShowErrorBox(e.Message); SaveError(e.Message, GetEIP); end; end; ... Esto agregaría una línea como la siguiente en el registro de errores: Lun 08-Ene-2001 15:38:47 - No se puede abrir el archivo project1.dpe @ 44747C Cuando alimente AVFinder con esta dirección (por ejemplo 44747C), verá que no corresponde con el comienzo del procedimiento (como sucedía cuando le pasábamos @TForm1.Button1Click a SaveError), sino que ahora corresponde a la línea SaveError. Esto es más útil puesto que en un procedimiento con más de un bloque try, podemos saber en cuál de ellos ocurrió la excepción. Pensando en simplificar las cosas aún más, sería lindo si SaveError pudiera determinar automáticamente la dirección del procedimiento o función llamador/a, relevándonos de tener que pasarla como parámetro. Para este propósito he modificado el primer procedimiento SaveError para que simplemente obtenga la dirección de retorno y se la pase al nuevo procedimiento SaveError: {$W+} // TURN STACKFRAMES ON procedure SaveError(const ErrMessage: string); overload; var Address: Pointer; begin asm // Address := %DirecciónDeRetorno%; mov edx, [ebp+4]; // EDX := (EBP+4)^; mov Address, edx; // Address := EDX; end; SaveError(ErrMessage, Address); end; Cuando un procedimiento usa un marco de pila, la dirección de retorno es apuntada por EBP+4 en vez de ESP. Activé los marcos de pila al principio para asegurarme que el procedimiento usará un marco de pila. Usé EDX en vez de EAX por razones de optimización (el registro EAX es automática- mente usado para contener el parámetro ErrMessage). Ahora podemos llamar a SaveError como antes (sin el parámetro Address), y la dirección será determinada automáticamente: procedure TForm1.Button1Click(Sender: TObject); ... try ... except on e: exception do begin ShowErrorBox(e.Message); SaveError(e.Message); end; end; ... UNA PEQUEÑA SIMPLIFICACION ========================== Para simplificar las cosas un poquito más para el programador, y aún así proveer más información en el registro de errores, he escrito varias versiones sobrecargadas de un procedimiento que llamé HandleError. Para un manejador de excepciones, estos procedimientos son útiles: procedure HandleError(E: Exception); overload; var Address: Pointer; begin asm // Address := %ReturnAddress%; mov ecx, [ebp+4]; // ECX := (EBP+4)^; mov Address, ecx; // Address := ECX; end; HandleError(nil, E, Address); end; procedure HandleError(Sender: TObject; E: Exception); overload; var Address: Pointer; begin asm // Address := %DirecciónDeRetorno%; mov eax, [ebp+4]; // ECX := (EBP+4)^; mov Address, eax; // Address := ECX; end; HandleError(Sender, E, Address); end; Como puede ver, ambos son envolturas para este procedimiento: procedure HandleError(Sender: TObject; E: Exception; Address: Pointer); overload; begin ShowErrorBox(E.Message); SaveError(Sender, E.ClassName + ': ' + E.Message, Address); end; Simplemente muestra el mensaje (llamando a ShowErrorBox), y luego llama a SaveError para guardarlo: procedure SaveError(Sender: TObject; const ErrMessage: string; Address: Pointer); overload; var ErrorMssg: string; begin ErrorMssg := ErrMessage; if Sender <> nil then begin ErrorMssg := ErrorMssg + ' ['; if Sender is TComponent then ErrorMssg := ErrorMssg + GetComponentFullName(TComponent(Sender)) else ErrorMssg := ErrorMssg + Sender.ClassName; ErrorMssg := ErrorMssg + ']'; end; SaveError(ErrorMssg, Address); end; Ahora en nuestros programas podemos usar un código como el siguiente, el cual es más simple: procedure TForm1.Button1Click(Sender: TObject); ... try ... except on e: exception do HandleError(Sender, e); end; ... Y la línea guardada en el registro de errores contiene más información: Lun 08-Ene-2001 15:38:47 - EFOpenError: No se puede abrir el archivo project1.dpe [Form1.Button1] @ 44747C La parte buena de pasar el parámetro Sender es que podemos obtener una idea rápida de donde ocurrió el error sin tener que usar AVFinder. - - - - - - - - - - - - - - - - - - - - - En el siguiente número les mostraré como atrapar excepciones no mane- jadas, errores API y errores propios. Espero que hayan encontrado este artículo útil hasta ahora. Si tienen dudas o preguntas pueden suscribirse a nuestra lista de correo de bajo tráfico. Pueden encontrar más información aquí: http://www.latiumsoftware.com/es/forums.html ________________________________________________________________________ 5. OBTENIENDO EL NOMBRE DE USUARIO DE UNA SESION DE WINDOWS Si necesitamos conocer el nombre con el que el usuario del sistema se ha logueado para iniciar su sesión en Windows, podemos echar mano a la función API de Windows GetUserName. La siguiente función encapsula la llamada a esta API para devolver el nombre de usuario como una cadena. uses Windows; function GetLoginName: string; var buffer: array[0..255] of char; size: dword; begin size := 256; if GetUserName(buffer, size) then Result := buffer else Result := '' end; Llamada de ejemplo: procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(GetLoginName); end; ________________________________________________________________________ 6. OBTENIENDO EL NOMBRE DE RED DE UNA MAQUINA Si queremos conocer el nombre que identifica la máquina que ejecuta nuestro programa en una red, podemos apelar a la función API de Windows GetComputerName que nos devuelve el nombre NetBIOS de la computadora local. La siguiente función encapsula la llamada a esta API para devolver el nombre de la máquina como una cadena. uses Windows; function GetComputerNetName: string; var buffer: array[0..255] of char; size: dword; begin size := 256; if GetComputerName(buffer, size) then Result := buffer else Result := '' end; Los usuarios de Windows 2000 pueden usar la función API GetComputerNameEx que además del nombre NetBIOS permite obtener diversas variantes de nombres DNS. Llamada de ejemplo: procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(GetComputerNetName); end; ________________________________________________________________________ 7. OBTENIENDO LA LETRA DE LA UNIDAD DE CD-ROM Para obtener la letra de la unidad correspondiente a la primera unidad de CD-ROM en un sistema haremos uso de dos funciones de la API de Windows: GetLogicalDriveStrings y GetDriveType. Con la primera recuperaremos la lista de de las unidades lógicas en un búfer. La lista es una secuencia de cadenas terminadas en nulo de cuatro caracteres de longitud (contando el terminador nulo), y termina en un caracter nulo, por ejemplo: 'a:\'#0'b:\'#0'c:\'#0'd:\'#0'f:\'#0#0 Con GetDriveType podemos determinar si una unidad determinada es una unidad de CD-ROM comprobando si el valor devuelto es igual a la constante DRIVE_CDROM. La siguiente función devuelve la primera unidad lógica que corresponde a una unidad de CDROM. La función devuelve la cadena vacía ('') si no se encontró ninguna unidad de CDROM. uses Windows; function GetFirstCdRomDrive: string; var r: LongWord; Unidades: array[0..128] of char; pUnidad: pchar; begin Result := ''; r := GetLogicalDriveStrings(sizeof(Unidades), Unidades); if r = 0 then exit; if r > sizeof(Unidades) then raise Exception.Create(SysErrorMessage(ERROR_OUTOFMEMORY)); pUnidad := Unidades; // Apunta a la primera unidad while pUnidad^ <> #0 do begin if GetDriveType(pUnidad) = DRIVE_CDROM then begin Result := pUnidad; exit; end; inc(pUnidad, 4); // Apunta a la siguiente unidad end; end; Llamada de ejemplo: procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(GetFirstCdRomDrive); end; ________________________________________________________________________ 8. INVOCANDO EL PROGRAMA DE CORREO PREDETERMINADO PARA ENVIAR UN EMAIL Puede invocar la ventana "Nuevo Mensaje" o "Componer Mensaje" del programa de correo electrónico predeterminado usando la función API ShellExecute declarada en la unidad ShellApi, simplemente pasándole 'mailto:' como tercer parámetro (lpFile), tal como se muestra aquí: uses ShellAPI; procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Self.Handle, nil, 'mailto:', nil, nil, SW_NORMAL); end; También puede agregar la dirección de email del destinatario: procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Self.Handle, nil, 'mailto:eds2004 @ latiumsoftware.com', nil, nil, SW_NORMAL); end; Incluso se puede incluir una línea de asunto: procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Self.Handle, nil, 'mailto:eds2004 @ latiumsoftware.com?Subject=Test', nil, nil, SW_NORMAL); end; Y hasta el texto del mensaje: procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Self.Handle, nil, 'mailto:eds2004 @ latiumsoftware.com' + '?Subject=Test&Body=Just testing the example', nil, nil, SW_NORMAL); end; NOTA: El protocolo mailto no soporta archivos adjuntos. ________________________________________________________________________ 9. ENLACES * JfControls. El más potente conjunto integrado de herramientas concebido para Delphi. Nueva librería de componentes con gestión completamente innovadora. http://www.jfactivesoft.com/spindex.htm * Ministry of Delphi - Proyecto IB Developer, componentes, artículos técnicos, descargas, enlaces, etc. http://www.madridsoft.com/ministryofdelphi * El Rinconcito de Delphi - Diccionario, tutorial, ideas útiles, iconos. http://www.elrinconcito.com/delphi/index.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/p0014.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) 2001 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!






