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
eds2008 @ latiumsoftware.com
________________________________________________________________________
JfControls Lib. Multilenguaje. Multiapariencia. Skins. Privilegios. Más
de 40 componentes integrados y personalizables. Múltiples problemas de
programación resueltos. Administración centralizada de recursos. Para
Delphi 3-2006 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm
________________________________________________________________________
2. 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:eds2008 @ 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:eds2008 @ 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:eds2008 @ 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/es/file.php?id=p14
________________________________________________________________________
Página principal: http://www.latiumsoftware.com/es/pascal/index.php
Página del grupo: http://espanol.groups.yahoo.com/group/boletin-pascal/
Para suscribirse / apuntarse: boletin-pascal-subscribe@gruposyahoo.com
Para cancelar / removerse: boletin-pascal-unsubscribe@gruposyahoo.com
Para reportar problemas con la suscripción: eds2008 @ latiumsoftware.com
________________________________________________________________________
Este boletín se provee "TAL Y COMO ESTA", sin garantía de ninguna clase.
Su uso implica la aceptación de nuestros términos de licencia y de la
ausencia de garantía que puedes leer en nuestro sitio web. Allí también
encontrarás una nota sobre marcas registradas. Te animamos a que redis-
tribuyas este boletín, siempre y cuando lo hagas en forma completa
(incluyendo la información de copyright), sin modificaciones y de manera
gratuita. Los artículos son copyright de sus respectivos autores y se
reproducen aquí con el permiso de los mismos.
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/es/index.php
Copyright (c) 2001 por Ernesto De Spirito. Todos los derechos reservados
________________________________________________________________________
|