15 junio 2007

Minimizar en la bandeja del sistema

Una de las características mas utilizadas en los programas P2P es la de minimizar nuestra aplicación en la bandeja del sistema (al lado del reloj de Windows en la barra de tareas).

Voy a mostraros como modificar el formulario principal de vuestra aplicación para que se minimize en la bandeja del sistema y una vez minimizado cuando se pulse sobre el icono se restaure. También vamos a añadir la posibilidad de pulsar dicho icono con el botón derecho del ratón y que muestre un menu contextual (popup) con la opción Mostrar.

Lo primero de todo es añadir un menu contextual a nuestro formulario principal (PopupMenu) con el nombre MenuBandeja. Añadimos una sola opción llamada Mostrar. A continuación añadimos en la sección uses del formulario principal la unidad ShellAPI:

uses
Windows, Messages, ...., ShellAPI;

Después en la sección private insertamos la variable:

IconData: TNotifyIconData;

En la misma sección private añadimos los procedimientos:

procedure WMSysCommand( var Msg: TWMSysCommand ); message WM_SYSCOMMAND;
procedure Restaurar( var Msg: TMessage ); message WM_USER+1;

Cuya implementación sería la siguiente:

procedure TFPrincipal.WMSysCommand( var Msg: TWMSysCommand );
begin
if Msg.CmdType = SC_MINIMIZE then
Minimizar
else
DefaultHandler( Msg );
end;

procedure TFPrincipal.Restaurar( var Msg: TMessage );
var p: TPoint;
begin
// ¿Ha pulsado el botón izquierdo del ratón?
if Msg.lParam = WM_LBUTTONDOWN then
MostrarClick( Self );

// ¿Ha pulsado en la bandeja del sistema con el botón derecho del ratón?
if Msg.lParam = WM_RBUTTONDOWN then
begin
SetForegroundWindow( Handle );
GetCursorPos( p );
MenuBandeja.Popup( p.x, p.y );
PostMessage( Handle, WM_NULL, 0, 0 );
end;
end;

El procedimiento WMSysCommand es el encargado de interceptar los mensajes del sistema que manda Windows a nuestra aplicación. En el caso de que el mensaje enviado sea SC_MINIMIZE minimizamos la ventana en la bandeja del sistema. Si es otro mensaje dejamos que Windows lo maneje (DefaultHandler).

El procedimiento Restaurar comprueba si ha pulsado el botón izquierdo del ratón sobre el icono de la bandeja del sistema para volver a mostrar nuestra ventana. Si pulsa el botón derecho llamará a nuestro menu contextual MenuBandeja.

Ahora creamos el procedimiento encargado de minimizar la ventana:

procedure TFPrincipal.Minimizar;
begin
with IconData do
begin
cbSize := sizeof( IconData );
Wnd := Handle;
uID := 100;
uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;
uCallbackMessage := WM_USER + 1;

// Usamos de icono el mismo de la aplicación
hIcon := Application.Icon.Handle;

// Como Hint del icono, el nombre de la aplicación
StrPCopy( szTip, Application.Title );
end;

// Ponemos el icono al lado del reloj
Shell_NotifyIcon( NIM_ADD, @IconData );

// Ocultamos el formulario
Hide;
end;

Y por último el evento al pulsar la opción Mostrar en el menú contextual:

procedure TFPrincipal.MostrarClick( Sender: TObject );
begin
// Volvemos a mostrar de nuevo el formulario
FPrincipal.Show;
ShowWindow( Application.Handle, SW_SHOW );

// Eliminamos el icono de la bandeja del sistema
Shell_NotifyIcon( NIM_DELETE, @IconData );
IconData.Wnd := 0;
end;

Aunque pueda parecer algo engorroso creo que es mas limpio que tener que instalar componentes para que realicen esto. Al fin y al cabo sólo hay que hacerlo sólo en el formulario principal.

Pruebas realizadas en Delphi 7.

14 junio 2007

Cómo ocultar una aplicación

Vamos a ver como hacer que una aplicación cualquiera hecha en Delphi quede oculta de la barra de tareas de Windows y del escritorio. Sólo podrá verse ejecutando el administrador de tareas en la pestaña Procesos.

Para ello vamos a añadir en el evento OnCreate del formulario principal de nuestra aplicación lo siguiente:

procedure TFPrincipal.FormCreate(Sender: TObject);
begin
// Hacemos que el formulario sea invisible poniendolo en la
// esquina superior izquierda, tamaño cero y aplicación invisible
BorderStyle := bsNone;
Left := 0;
Top := 0;
Width := 0;
Height := 0;
Visible := False;
Application.Title := '';
Application.ShowMainForm := False;

// Lo ocultamos de la barra de tareas
ShowWindow( Application.Handle, SW_HIDE );
SetWindowLong( Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or
WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
end;

Esto nos puede ser util para crear programas residentes ocultos al usuario para administración de copias de seguridad, reparación automática de bases de datos y envío de mailing automátizado.

Pruebas realizadas en Delphi 7.

13 junio 2007

Capturar el teclado en Windows

Hay ocasiones en las cuales nos interesa saber si una tecla de Windows ha sido pulsada aunque estemos en otra aplicación que no sea la nuestra.

Por ejemplo en el artículo anterior mostré como capturar la pantalla. Sería interesante que si pulsamos F8 estando en cualquier aplicación nos capture la pantalla (incluso si nuestra aplicación esta minimizada).

Para ello vamos a utilizar la función de la API de Windows GetAsyncKeyState la cual acepta como parámetro la tecla pulsada (VK_RETURN, VK_ESCAPE, VK_F8, etc) y nos devuelve -32767 si la tecla ha sido pulsada.

Como el teclado hay que leerlo constantemente y no conviene dejar un bucle cerrado consumiendo mucho procesador, lo que vamos a hacer es meter a nuestro formulario un temporizador TTimer activado cada 10 milisegundos (Inverval) y con el evento OnTimer definido de la siguiente manera:

procedure TFormulario.TemporizadorTimer( Sender: TObject );
begin
// ¿Ha pulsado una tecla?
if GetAsyncKeyState( VK_F8 ) = -32767 then
CapturarPantalla;
end;

Para capturar números o letras se hace con la función ord:

if GetAsyncKeyState( Ord( 'A' ) ) then ...
if GetAsyncKeyState( Ord( '5' ) ) then ...

Si es una letra hay que pasarla a mayúsculas.

Sólo con esto podemos interceptar cualquier tecla del buffer de Windows. Por ejemplo se podría hacer una aplicación que al pulsar F10 minimize todas las ventanas de Windows.

Pruebas realizadas en Delphi 7.

12 junio 2007

Capturar la pantalla de Windows

Vamos a crear un procedimiento que captura un trozo de la pantalla de Windows y la guarda en un bitmap:

procedure CapturarPantalla( x, y, iAncho, iAlto: Integer; Imagen: TBitmap );
var
DC: HDC;
lpPal : PLOGPALETTE;
begin
if ( iAncho = 0 ) OR ( iAlto = 0 ) then
Exit;

Imagen.Width := iAncho;
Imagen.Height := iAlto;
DC := GetDc( 0 );

if ( DC = 0 ) then
Exit;

if ( GetDeviceCaps( dc, RASTERCAPS) and RC_PALETTE = RC_PALETTE ) then
begin
GetMem( lpPal, SizeOf( TLOGPALETTE ) + ( 255 * SizeOf( TPALETTEENTRY ) ) );
FillChar( lpPal^, SizeOf( TLOGPALETTE ) + ( 255 * SizeOf( TPALETTEENTRY ) ), #0 );
lpPal^.palVersion := $300;
lpPal^.palNumEntries := GetSystemPaletteEntries( DC, 0, 256, lpPal^.palPalEntry );

if (lpPal^.PalNumEntries <> 0) then
Imagen.Palette := CreatePalette( lpPal^ );

FreeMem( lpPal, SizeOf( TLOGPALETTE ) + ( 255 * SizeOf( TPALETTEENTRY ) ) );
end;

BitBlt( Imagen.Canvas.Handle, 0, 0, iAncho, iAlto, DC, x, y, SRCCOPY );
ReleaseDc( 0, DC );
end;

Resumiendo a grandes rasgos lo que hace el procedimiento es crear un dispositivo de contexto donde según el número de bits por pixel reserva una zona de memoria para capturar el escritorio. Después mediante la función BitBlt vuelca la imagen capturada al Canvas de la imagen que le pasamos.

Para capturar toda la pantalla de Windows utilizando este procedimiento hacemos lo siguiente:

var Imagen: TBitmap;
begin
Imagen := TBitmap.Create;
CapturarPantalla( 0, 0, Screen.Width, Screen.Height, Imagen );
Imagen.SaveToFile( ExtractFilePath( Application.ExeName ) + 'captura.bmp' );
Imagen.Free;
end;

La pantalla capturada la guarda en el archivo captura.bmp al lado de nuestro ejecutable. Sólo faltaría el poder capturar una tecla de Windows desde cualquier aplicación para activar nuestro capturador de pantalla (para que no se capture a si mismo).

Pruebas realizadas en Delphi 7.

11 junio 2007

Descargando archivos por FTP con INDY

El compononente IdFTP que utilizamos en el artículo anterior para subir archivos es el mismo que vamos a utilizar para descargarlos. Añadimos a la sección interface:

uses
Windows, Messages, ......, IdFTP, IdComponent;

Y creamos el procedimiento para descargar el archivo:

procedure DescargarArchivo( sArchivo: String );
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create( nil );
FTP.OnWork := FTPWork;
FTP.Username := 'usuario';
FTP.Password := 'miclave';
FTP.Host := 'miftp.midominio.com';

try
FTP.Connect;
except
raise Exception.Create( 'No se ha podido conectar con el servidor ' + FTP.Host );
end;

FTP.ChangeDir( '/misarchivos/copiaseguridad/' );

Antes de comenzar la descarga hay que averiguar el tamaño del archivo en el servidor:

Barra.Max := FTP.Size( ExtractFileName( sArchivo ) ) div 1024;

Donde Barra es un objeto TProgressBar que colocamos para mostrar al usuario el progreso de la descarga. Ahora nos aseguramos de que el archivo a descargar no haya sido descargado anteriormente, ya que podría producir un error:

if FileExists( sArchivo ) then
DeleteFile( sArchivo );

Para descargar el archivo utilizaremos el método Get, el cual toma como primer parámetro la ruta y nombre del archivo a descargar en local, como segundo parámetro el nombre que va a tener el archivo en el servidor, como tercer parámetro si deseamos añadir a un archivo ya existente y como último parámetro si deseamos la opción RESUME (en caso de interrumpirse la conexión si queremos que continue por donde iba, siempre y cuando el servidor soporte dicho modo).

FTP.Get( ExtractFileName( sArchivo ), sArchivo, False, False );

Para finalizar nos desconectamos del servidor y eliminamos el objeto.

FTP.Disconnect;
FTP.Free;
end;

También creamos el evento OnWork para controlar el progreso de la descarga:

procedure TFVentana.FTPWork( Sender: TObject; AWorkMode: TWorkMode; const AWorkCount: Integer );
begin
Barra.Position := AWorkCount div 1024;
end;

Para terminar recomiendo meter el método Get en un hilo de ejecución por si el componente se queda bloqueado en la descarga.

Pruebas realizadas en Delphi 7.

Publicidad