PDA

Ver la Versión Completa : Mostrar ventana con cuaderno de bitácora en programa automático


Ñuño Martínez
19-01-2007, 13:24:28
Pues estaba yo codificando y me he dado cuenta de un detalle tonto.

Tengo que hacer un programa que funciona solo. Se le mete en el "programador de eventos" para que se ejecute, por ejemplo, cada 24h y él trabaja solito y cuando termina su tarea pues se cierra y hasta la siguiente. Se me ocurrió que, en lugar de ser silencioso, abriera una ventana principal con TMemo de sólo lectura en el que fuera escribiendo qué es lo que va haciendo y, cuando termine, que lo vuelque todo en un archivo de texto a modo de cuaderno de bitácora (log). Si se produce un error durante la ejecución, el programa no se cierra y así se puede ver cuándo ha fallado y qué es lo que estaba haciendo cuando se rompió. Hasta aquí todo bien.

Ahora viene lo gordo. Estoy utilizando el evento OnCreate para inicializar el programa, pero no sé como "comenzar el trabajo". Por ahora he hecho algo así como:
(* TVentanaPrincipal::FormCreate:
* Inicialización, en respuesta a la creación de la ventana. *)
procedure TVentanaPrincipal.FormCreate(Sender: TObject);
begin
Log ('Inicio del programa');
InicializaPrograma;
Log ('Carga la configuración');
Configuracion := DVC_CONFIGURACION.create;
Configuracion.LeerDe (NombreArchivoConf);
EscribeConsola (LogTab + 'Configuración leída');
{ Mostramos la ventana. }
WindowState := wsNormal;
Show;
{ Lo ponemos a trabajar. }
RealizaTrabajo;
end;
Pero claro, no me parece muy correcto y aunque por ahora funciona tal vez me de problemas más adelante. También había pensado en quitar el "Show" y el "RealizaTrabajo" de este evento y ponerlas en el evento "onShow", pero tampoco. Como el trabajo (por ahora) es muy corto el progama termina casi antes de mostrar la ventana, en ambos casos, así que no sé si lo hace bien o no.

Se me había ocurrido hacerlo todo en el módulo principal (el archivo "dpr"). Ahora este archivo es así:
begin
TRY
Application.Initialize;
Application.CreateForm(TVentanaPrincipal, VentanaPrincipal);
Application.Run;
EXCEPT
ON E:Exception DO
ShowMessage ('¡¡¡¡¡¡Excepción incontrolable!!!!!!' + #13 + E.Message);
END;
end;Y yo querría hacer algo así como:
begin
TRY
Application.Initialize;
Application.CreateForm(TVentanaPrincipal, VentanaPrincipal);
WITH VentanaPrincipal DO
BEGIN
Log ('Inicio del programa');
InicializaPrograma;
Log ('Carga la configuración');
Configuracion := DVC_CONFIGURACION.create;
Configuracion.LeerDe (NombreArchivoConf);
EscribeConsola (LogTab + 'Configuración leída');
{ Mostramos la ventana. }
WindowState := wsNormal;
Show;
{ Lo ponemos a trabajar. }
RealizaTrabajo;
{ Terminamos. }
Free;
EXCEPT
ON E:Exception DO
ShowMessage ('¡¡¡¡¡¡Excepción incontrolable!!!!!!' + #13 + E.Message);
END;
end;Evidentemente eliminaría todo el código de los eventos de la ventana. Sin embargo, eso de quitar el "Application.Run" no termina de gustarme, y si lo pongo no me ejecutará el resto, ¿o sí?

¿Me arriesgo o alguien conoce una forma mejor de hacerlo?

seoane
19-01-2007, 13:45:21
Se me ocurren varias posibilidades, la primera es hacer una aplicación de consola, con sus writeln :D , me parece que se ajusta bastante bien a lo que quieres. Es mas, nada te impide utilizar en la misma aplicación la consola y ventanas, por si mas adelante en tu aplicación necesitas ventanas.

Si la idea de la consola no te gusta, podemos recurrir a los mensajes. En el evento OnCreate posteamos un mensaje, y cuando lo recibamos la ventana ya se estará mostrando. Algo así:


const
WM_TRABAJAR = WM_USER + 100;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
// Aqui inicializa tu aplicacion

// Posteamos el mensaje
PostMessage(Handle,WM_TRABAJAR,0,0);
end;

procedure TForm1.WMTRABAJAR(var Msg: TMessage);
var
i: integer;
begin
for i:= 1 to 3 do
begin
Memo1.Lines.Add('Hola');
Application.ProcessMessages;
Sleep(1000);
end;
end;

Bicho
19-01-2007, 13:48:46
Hola,

La verdad, no pillo el problema o la dificultad que tienes. :confused:

Pero si dices
Como el trabajo (por ahora) es muy corto el progama termina casi antes de mostrar la ventana, en ambos casos, así que no sé si lo hace bien o no.

Si tan poco tiempo dura, ¡no muestres nada!
Ahora me explico:

Tengo que hacer un programa que funciona solo. Se le mete en el "programador de eventos" para que se ejecute, por ejemplo, cada 24h y él trabaja solito y cuando termina su tarea pues se cierra y hasta la siguiente. Se me ocurrió que, en lugar de ser silencioso, abriera una ventana principal con TMemo de sólo lectura en el que fuera escribiendo qué es lo que va haciendo y, cuando termine, que lo vuelque todo en un archivo de texto a modo de cuaderno de bitácora (log). Si se produce un error durante la ejecución, el programa no se cierra y así se puede ver cuándo ha fallado y qué es lo que estaba haciendo cuando se rompió. Hasta aquí todo bien.

Yo casi todas las aplicaciones que realizo en mi trabajo son de este tipo, o están en marcha todo el día funcionando solos, o se ejecutan a una hora determinada, el programador de tareas lo abre, y cuando termina, se cierra la aplicación y hasta la próxima ejecución.
Bien, no me parece buena idea lo de guardar el log al final de la ejecución, sino siempre que añadas algo.
Así tengo una función, a la que le pasas como parámetro el texto que quieres guardar (como muy bien has hecho tú tambien), y esta función abre el fichero de log, inserta esa linea, junto con la fecha y hora del sistema (usando formatdatetime para una mejor lectura), y graba el fichero.
Es un fichero de texto y su acceso, como sabes, es muy rápido, y los programas no relentizan nada.
Lo digo, porque así siempre tendrás el fichero con el log actualizado, si lo guardas en un memo, por ejemplo y al final lo guardas, te arriesgas a que por cualquier cosa, se cierre de mala manera (KillTask, apagón, etc) y pierdes el log; sabrás que ha ido mal y no tendrás el log para averiguarlo.
Además de esta manera, no necesitas visualizar una ventana con el log, si es allí donde tienes los problemas.

Como comentario al código que has puesto, sólo decirte que no parece buena idea, mostrar la ventana que deseas en el OnCreate de la aplicación u OnShow, sino mostrarlo cuando la aplicación esté activa, es decir, en el OnActivate.

Bueno ya me comentas si te he entendido, si me explicas que ocurre y seguimos conversando y si te sirve de ayuda mi experiencia previa.

Saludos

Ñuño Martínez
19-01-2007, 14:10:19
Como dijo nuestro querido Jack, "vayamos por partes":
Se me ocurren varias posibilidades, la primera es hacer una aplicación de consola, con sus writeln , me parece que se ajusta bastante bien a lo que quieres. Es mas, nada te impide utilizar en la misma aplicación la consola y ventanas, por si mas adelante en tu aplicación necesitas ventanas.Tienes toda la razón. No sé por qué no se me había ocurrido antes. Puede que haya sido la emoción de volver a utilizar el editor de Delphi después de tantos años, con sus botoncitos, su generador de ventanas, su RAD... Se me olvidó que puedo crear una aplicación de consola corriente y moliente.

Yo casi todas las aplicaciones que realizo en mi trabajo son de este tipo, o están en marcha todo el día funcionando solos, o se ejecutan a una hora determinada, el programador de tareas lo abre, y cuando termina, se cierra la aplicación y hasta la próxima ejecución.
Bien, no me parece buena idea lo de guardar el log al final de la ejecución, sino siempre que añadas algo.
Así tengo una función, a la que le pasas como parámetro el texto que quieres guardar (como muy bien has hecho tú tambien), y esta función abre el fichero de log, inserta esa linea, junto con la fecha y hora del sistema (usando formatdatetime para una mejor lectura), y graba el fichero.
Es un fichero de texto y su acceso, como sabes, es muy rápido, y los programas no relentizan nada.
Lo digo, porque así siempre tendrás el fichero con el log actualizado, si lo guardas en un memo, por ejemplo y al final lo guardas, te arriesgas a que por cualquier cosa, se cierre de mala manera (KillTask, apagón, etc) y pierdes el log; sabrás que ha ido mal y no tendrás el log para averiguarlo.También tú tienes razón, me había olvidado de que el programa puede morir por muchos motivos.

Decididamente lo haré por consola y guardando en el archivo línea a línea, en lugar de usar el TMemo.

Por cierto, para hacer la configuración voy a tener que mostrar una ventana. ¿Servirá algo así?
BEGIN
Application.Initialize;
IF argv[1] = "-config" THEN
WITH TVentanaConfig.Create DO
BEGIN
ShowModal;
WHILE NOT Acepta DO
Application.ProcessMessages;
GuardaConfiguracion;
Free;
END
ELSE
LeeConfiguracion;
Trabajo;
END;

Evidentemente, "Acepta" es una propiedad de la ventana que se pone a TRUE al pulsar el botón aceptar.

Bicho
19-01-2007, 14:17:25
Tienes toda la razón. No sé por qué no se me había ocurrido antes. Puede que haya sido la emoción de volver a utilizar el editor de Delphi después de tantos años, con sus botoncitos, su generador de ventanas, su RAD... Se me olvidó que puedo crear una aplicación de consola corriente y moliente.

Pues sí no habia caido en hacer aplicaciones de consola (como no he hecho ninguna en Delphi, pues no se me pasa por la cabeza hacerlas), pero no veo inconveniente.

Personalmente opino, (y quizá sea una tontería), pero si es una aplicación que no tiene que verse, no muestre ninguna ventana incluso para la configuración.

Pero OJO:
a configuración entiendo, como parámetros de la aplicación y no como un filtro de datos. En pocas palabras un fichero .INI
Para ello, si estoy empezando a optar (en caso de aplicaciones "invisibles"), que la configuración se haga desde otro programa. Y tú aplicación de consola, abre el INI y guarda los valores en variables.

Pero, oye, es mi opinión ;)

Saludos

fdelamo
19-01-2007, 14:19:09
Yo cambiaría esta parte


WITH TVentanaConfig.Create DO
BEGIN
ShowModal;
WHILE NOT Acepta DO
Application.ProcessMessages;
GuardaConfiguracion;
Free;
END


por esto


WITH TVentanaConfig.Create DO
BEGIN
if ShowModal = mrYes then
GuardaConfiguracion;
Free;
END


al hacer el ShowModal ya se para la ejecución hasta que se haga el ModalResult del formulario

Ñuño Martínez
19-01-2007, 14:39:55
Gracias Bicho, por sugerir lo de utilizar dos programas (uno para configurar y el otro que haga el trabajo), al final va a ser lo más cómodo.

Y gracias fdelamo por corregirme lo del "ShowModal". Olvidé que las ventanas "modales" devuelven el "ModalResult" cuando se cierran.

Estoy de un olvidadizo subido... :p

seoane
19-01-2007, 15:59:38
Bueno, yo sigo dandole vueltas al asunto :D

Se me ocurre tener 2 aplicaciones, una es el propio programa y la otra vigila a la primera. El vigilante seria una aplicación como esta:

program Bitacora;

{$APPTYPE CONSOLE}

uses
Windows,
SysUtils;

function IsWinNT: boolean;
var
Osv: OSVERSIONINFO;
begin
Osv.dwOSVersionInfoSize := SizeOf(Osv);
GetVersionEx(Osv);
Result := OSV.dwPlatformId = VER_PLATFORM_WIN32_NT;
end;

function Min(i,j: Cardinal): Cardinal;
begin
if i < j then
Result:= i
else
Result:= j;
end;

procedure Loop(Cmd: string);
var
Buffer: PChar;
Si: STARTUPINFO;
Sa: SECURITY_ATTRIBUTES;
Sd: SECURITY_DESCRIPTOR;
Pi: PROCESS_INFORMATION;
NewStdin, NewStdout, Read_Stdout, Write_Stdin: THandle;
Exitcod, Bread, Avail: Cardinal;
begin
if IsWinNT then
begin
InitializeSecurityDescriptor(@Sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@Sd, TRUE, nil, FALSE);
Sa.lpSecurityDescriptor := @Sd;
end else Sa.lpSecurityDescriptor := nil;
Sa.nLength := SizeOf(SECURITY_ATTRIBUTES);
Sa.bInheritHandle := TRUE;
if CreatePipe(NewStdin, Write_Stdin, @Sa, 1) then
begin
if CreatePipe(Read_stdout, Newstdout, @Sa, 1) then
begin
GetStartupInfo(Si);
with Si do
begin
dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
wShowWindow := SW_SHOW;
hStdOutput := NewStdout;
hStdError := NewStdout;
hStdInput := NewStdin;
end;
if CreateProcess(nil, PChar(Cmd), nil, nil, TRUE, CREATE_NEW_CONSOLE,
nil, nil, Si, Pi) then
begin
GetMem(Buffer,4096);
try
repeat
if not PeekNamedPipe(Read_Stdout, nil, 0, nil, @Avail, nil) then
break;
if Avail > 0 then
begin
FillChar(Buffer^, 4096, 0);
ReadFile(Read_Stdout, Buffer^, Min(Avail,4095), Bread, nil);
if Bread > 0 then Write(String(Buffer));
// Aqui podiamos guardarlo en un archivo por ejemplo
end else
Sleep(10);
GetExitCodeProcess(Pi.hProcess, Exitcod);
until (exitcod <> STILL_ACTIVE);
finally
FreeMem(Buffer);
end;
end;
CloseHandle(Read_Stdout);
CloseHandle(NewStdout);
end;
CloseHandle(NewStdin);
CloseHandle(Write_Stdin);
end;
end;

var
i: integer;
Str: String;
begin
Str:= ParamStr(1);
for i:= 2 to ParamCount do
Str:= Str + #32 + ParamStr(i);
if Str <> EmptyStr then
Loop(Str);
// Esto es solo para que no se cierre inmediatamente
Readln;
end.


Ahora en la aplicación principal usaríamos un procedure tal como este:

procedure log(Str: string);
var
StdError: THandle;
begin
StdError:= GetStdHandle(STD_ERROR_HANDLE);
if (StdError <> STD_ERROR_HANDLE) and (StdError <> 0) then
begin
FileWrite(StdError,PChar(Str + #13#10)^,Length(Str)+2);
end;
end;

// Por ejemplo
log('Hola Mundo');


De esta manera, si iniciamos la aplicación principal solamente, el procedure no tendrá ningún efecto. Sin embargo, si la ejecutamos desde la aplicación bitácora, esta mostrara los mensajes. Por ejemplo:

Bitacora.exe Principal.exe /Algunparametro


:confused: ¿Sera matar moscas a cañonazos? :p

Ñuño Martínez
19-01-2007, 16:03:55
¡Corcho! :eek: Hoy ya no me da tiempo a mirarlo con detenimiento, pero lo haré el lunes sin falta.

seoane, ¿tú no trabajas ni tienes una carrera pendiente ni nada? ;):D

seoane
19-01-2007, 16:08:43
seoane, ¿tú no trabajas ni tienes una carrera pendiente ni nada? ;):D


Pues no, y no :p :D

Bicho
19-01-2007, 17:12:41
Ya está el agonioso que todo lo sabe escribiendo código, otra vez :D :p :D

Pero Domingo, éste es un foro de habla hispana, ¿podrias repetir tu propuesta en castellano, para que te entendamos los demás? :confused:
Por que yo me he quedado igual que si no lo hubiera visto... :eek:

Saludos

Ñuño Martínez
22-01-2007, 14:45:27
Pues después de leerme el código fuente (no lo he probado) puedo decir que sí: es matar moscas a cañonazos... :p :D

seoane
22-01-2007, 15:24:18
Pues después de leerme el código fuente (no lo he probado) puedo decir que sí: es matar moscas a cañonazos... :p :D

:( Como te pasas Ñuño ... Jeje, :p

Ya dije que podía ser excesivo, pero hay que reconocer que usar las propias salidas estándar es una solución muy elegante.

Ñuño Martínez
22-01-2007, 16:46:54
:( Como te pasas Ñuño ... Jeje, :p

Ya dije que podía ser excesivo, pero hay que reconocer que usar las propias salidas estándar es una solución muy elegante.

No, si yo lo decía por tener que usar dos programas distintos. Al final me he hecho una clase muy sencillota que abre el archivo al crearse y lo cierra al destruirs, con un par de métodos a los que le paso lo que quiero escribir en él y en la pantalla.

seoane
22-01-2007, 17:48:53
No, si yo lo decía por tener que usar dos programas distintos. Al final me he hecho una clase muy sencillota que abre el archivo al crearse y lo cierra al destruirs, con un par de métodos a los que le paso lo que quiero escribir en él y en la pantalla.

Solo por fastidiar :p ¿Y si el programa se cuelga, o se cierra de forma brusca?

Controlándolo desde otro programa podremos obtener mas datos, que desde el mismo programa. Incluso en caso de error grave podemos realizar alguna acción: ejecutar el programa de nuevo, enviar un reporte, acordarnos de la madre del usuario ... :p

chico_bds
22-01-2007, 18:14:04
Código Delphi [-] (http://www.clubdelphi.com/foros/#)const WM_TRABAJAR = WM_USER + 100; ... procedure TForm1.FormCreate(Sender: TObject); begin // Aqui inicializa tu aplicacion // Posteamos el mensaje PostMessage(Handle,WM_TRABAJAR,0,0); end; procedure TForm1.WMTRABAJAR(var Msg: TMessage); var i: integer; begin for i:= 1 to 3 do begin Memo1.Lines.Add('Hola'); Application.ProcessMessages; Sleep(1000); end; end;



Hola Seoane por favor si pudieras aclarme un poco esto de los mensajes en un aplicacion.

Que ventajas tiene usarlo?

O sea me refiero a:
Application.ProcessMessages;
TMessage

Saludos y desde ya muchas gracias

roman
22-01-2007, 23:03:04
Solo por fastidiar :p ¿Y si el programa se cuelga, o se cierra de forma brusca?

Controlándolo desde otro programa podremos obtener mas datos, que desde el mismo programa.

¿Y si el programa vigilante se cuelga, o se cierra de forma brusca? :p

Ahora, el código que pusiste más arriba imagino que es para redireccionar la entrada y salida estandar para que el programa vigilante lea lo que el programa principal escribe en la consola. Pero, el programa principal ¿no podría mejor usar un memory mapped file para pasar información al otro?

Mmmm..

Aunque de la manera que planteas, el programa principal igual saca sus mensajes aun en la eventualidad de no tener vigilante.

Pues viéndolo así, no me parece un cañonazo. Es decir, que me parece una opción muy adecuada.

// Saludos

seoane
22-01-2007, 23:46:14
¿Y si el programa vigilante se cuelga, o se cierra de forma brusca? :p


Pues que se lleva el programa vigilado consigo, es decir se cuelgan los 2. Esto no lo había pensado, y es un problema bastante gordo :D

Voy a tener que guardar el cañón ... :p

roman
22-01-2007, 23:53:35
Puedes poner un meta-vigilante que levante al vigilante. Y ya entrados en gastos, un meta-meta-vigilante que levante al meta-vigilante, aunque sería más seguro si agregases un meta-meta-meta-vigilante que levante al meta-meta-vigilante

:p

Es nada más por fastidiar :p, repito que me parece que el método que explicaste es muy buena opción.

// Saludos

seoane
23-01-2007, 00:11:03
Pues roman, es una de las ideas que me ronda la cabeza cuando estoy aburrido, como hacer, sin meternos en temas de permisos y demás, que una aplicación no se pueda cerrar. La idea, y ya estoy sacando el cañón :p , es hacer dos programas que se vigilen mutuamente, así cuando uno cae el otro lo vuelve a levantar, se vigilan las espaladas el uno al otro.

Pero nada, sera mejor que vuelva a guardar el cañón, y que cada programa se escriba sus propios logs y en casos mas complicados usar OutputDebugString que para eso esta.