Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   API de Windows (https://www.clubdelphi.com/foros/forumdisplay.php?f=7)
-   -   Ejecutar aplicacion externa y que este en primer plano (https://www.clubdelphi.com/foros/showthread.php?t=12104)

Lorenzati 05-07-2004 23:49:20

Ejecutar aplicacion externa y que este en primer plano
 
Hola, necesito ejecutar una aplicacion externa (lo estoy haciendo con WinExec('C:\winnt\notepad.exe',1) ) y que este siempre en primer plano sin importar las aplicaciones que se abran despues. Quiero hacer un timer que cada x segundos muestre la aplicacion pero no se como hacerlo.

Muchas gracias

roman 06-07-2004 04:47:30

Puedes intentar encontrar el identificador de la ventana de la aplicación externa usando FindWindow. Una vez que lo obtengas usas la función SetWindowPos para colocarla siempre visible:

Código Delphi [-]
const
  Flags = SWP_NOMOVE or SWP_NOSIZE;

var
  NotePadWindow: HWnd;

begin
  NotePadWindow := FindWindow('Notepad', nil); 
  SetWindowPos(NotePadWindow, HWND_TOPMOST, 0, 0, 0, 0, Flags);
end;

// Saludos

roman 06-07-2004 07:13:12

Hola, estuve revisando este tema. El problema con el método anterior es que el usuario podría tener abiertas otras instancias de la aplicación externa y no hay garantía de que FindWindow te regrese la ventana que tú abriste en lugar de otra.

Un método un poco más complicado consiste en abrir la aplicación directamente con CreateProcess para obtener el identificador del hilo de la aplicación:

Código Delphi [-]
{
  Abre la aplicación indicada en AppPath y
  devuelve el hilo de la nueva aplicación.
}
function ExecuteProcess(AppPath: String): Cardinal;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;

begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb := SizeOf(StartInfo);

  if not CreateProcess(
    PChar(AppPath), nil, nil, nil, false, 0, nil, nil, StartInfo, ProcInfo
  )
  then
    RaiseLastOSError;

  Result := ProcInfo.dwThreadId;
end;

Ahora la cuestión es obtener el identificador de la ventana principal asociada al hilo. El problema es que el concepto de ventana principal en realidad es poco claro. Hay aplicaciones, como Word, donde todas las ventanas abiertas son principales y simplemente la última en cerrarse es la que manda cerrar la aplicación.

De cualquier manera la siguiente función GetMainWindow que sigue te devolverá el identificador de la primera ventana que encuentre y que tenga "aspecto" de ser la principal:

Código Delphi [-]
function DoEnum(Handle: HWnd; Param: LParam): Bool; stdcall;
begin
  if
    IsWindowVisible(Handle)
    and IsWindowEnabled(Handle)
  then
  begin
    PInteger(Param)^:= Handle;
    Result := false;
  end
  else
    Result := true;
end;

{
  Recorre todas las ventanas principales asociadas
  al hilo hasta encontrar una que esté visible y activa.

  EnumThreadWindows llama a la función DoEnum para
  cada ventana encontrada.
}
function GetMainWindow(ThreadId: Cardinal): HWnd;
begin
  Result := 0;
  EnumThreadWindows(ThreadId, @DoEnum, Integer(Pointer(@Result)));
end;

Cuando quieras poner la aplicación como siempre visible usarías algo como

Código Delphi [-]
procedure MakeAppTopMost(ThreadId: Cardinal);
const
  Flags = SWP_NOMOVE or SWP_NOSIZE;

var
  WindowId: HWnd;

begin
  WindowId := GetMainWindow(ThreadId);

  BringWindowToTop(WindowId);
  if IsIconic(WindowId) then
    ShowWindow(WindowId, SW_RESTORE);

  SetWindowPos(WindowId, HWND_TOPMOST, 0, 0, 0, 0, Flags);
end;

pasándole el ThreadId obtenido con ExecuteProcess. La llamada a BringWindowToTop no parece ser necesaria para aplicaciones sencillas como el block de notas pero sí para aplicaciones como Word.

Posteriormente puedes usar el ThreadId obtenido para cerrar la aplicación externa:

Código Delphi [-]
procedure CloseApp(ThreadId: Cardinal);
var
  WindowId: HWnd;

begin
  WindowId := GetMainWindow(ThreadId);
  SendMessage(WindowId, WM_CLOSE, 0, 0);
end;

Nota que tanto MakeAppTopMost como CloseApp vuelven a calcular el identificador de ventana a partir del identificador de hilo. Esto es así porque el identificador de ventana puede cambiar a lo largo de la vida de la aplicación. Por ejemplo, en una aplicación hecha en Delphi, si durante la ejecución cambias el estilo del borde de la ventana, ésta se crea nuevamente dando un nuevo identificador. En cambio, el identificador de hilo permanece inmutable durante toda la ejecución de una aplicación.

// Saludos

delphi.com.ar 06-07-2004 15:39:56

Cita:

Empezado por roman
Hola, estuve revisando este tema. El problema con el método anterior es que el usuario podría tener abiertas otras instancias de la aplicación externa y no hay garantía de que FindWindow te regrese la ventana que tú abriste en lugar de otra.

Un método un poco más complicado consiste en abrir la aplicación directamente con CreateProcess para obtener el identificador del hilo de la aplicación

Haberlo visto antes!... tengo código escrito al respecto... :)
Un detalle, en mi código cuando busco la ventana perteneciente al hilo de ejecución, manejo un timeout interno porque sucede que si el equipo no es muy veloz, es posible que el proceso esté creado pero no la ventana.

Saludos!

roman 06-07-2004 17:21:58

Cita:

Empezado por delphi.com.ar
Un detalle, en mi código cuando busco la ventana perteneciente al hilo de ejecución, manejo un timeout interno porque sucede que si el equipo no es muy veloz, es posible que el proceso esté creado pero no la ventana.

Tienes razón. Aún en máquinas rápidas si la aplicación que se abre tarda mucho, como Word. No me había percatado porque hacía las pruebas por separado: en un botón mandaba abrir la aplicación y en otro botón la ponía como siempre visible.

Intente modificar GetMainWindow así

Código Delphi [-]
function GetMainWindow(ThreadId: Cardinal): HWnd;
begin
  Result := 0;

  while (Result = 0) do
    EnumThreadWindows(ThreadId, @DoEnum, Integer(Pointer(@Result)));
end;

pero no resulta. Aun cuando se obtiene el identificador de la ventana principal si la llamada a MakeAppTopMost es inmediata no siempre funciona.

Finalmente cambié ExecuteProcess:

Código Delphi [-]
function ExecuteApp(AppPath: String; TimeOut: Integer = 2000): Cardinal;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;

begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb := SizeOf(StartInfo);

  if not CreateProcess(
    PChar(AppPath), nil, nil, nil, false, 0, nil, nil, StartInfo, ProcInfo
  )
  then
    RaiseLastOSError;

  Sleep(TimeOut);

  Result := ProcInfo.dwThreadId;
end;

Así, por defecto esperará 2 segundos antes de proseguir pero se le puede cambiar el valor en la llamada.

// Saludos

delphi.com.ar 06-07-2004 17:38:17

Yo había creado unas funciones mas genéricas, tu "GetMainWindow" es algo así:
Código Delphi [-]
function FindThreadWindow(dwThreadId: DWORD; hParent: HWND; lpClassName, lpWindowName: PAnsiChar; wTimeOut: Word = 30000): HWND;
var
  AFindWindow: TFindWindow;
  ATickCount: DWORD;
begin
  AFindWindow.lpClassName := lpClassName;
  AFindWindow.lpWindowName := lpWindowName;
  AFindWindow.hResult := 0;
  ATickCount := GetTickCount + wTimeOut;
  repeat
    if hParent = 0 Then
      EnumThreadWindows(dwThreadId, @EnumThreadWndProc, Integer(@AFindWindow))
    else
      EnumChildWindows(hParent, @EnumThreadWndProc, Integer(@AFindWindow));
  until (AFindWindow.hResult <> 0) or (ATickCount < GetTickCount);

  Result := AFindWindow.hResult;
end;
Me parece que es un poco mas real el concepto de TimeOut, en tu código es un tiempo de espera. (Solo una cuestión semántica :))
Vale aclarar que usar GetTickCount no es del todo seguro para estos casos, ya que si el equipo estuvo encendido durante 49.7 días, este vuelve a cero. Un poco extraño, pero en uno de los proyectos en que estoy trabajando ha sucedido :D.

roman 06-07-2004 17:44:13

Bueno, pero, básicamente es mi while Result = 0 do (creo :confused: ). El caso es que probé esto y aún habiendo "encontrado" la ventana, la llamada a MakeAppTopMost no tenía efecto. Yo supongo que, como es normal en las aplicaciones Windows, luego de creada la ventana viene una llamada a SetWindowPos y si no ha pasado suficiente tiempo puede ser que ésta se haga después de la que nosotros hacemos.

// Saludos

roman 06-07-2004 17:58:46

Cita:

Empezado por delphi.com.ar
ya que si el equipo estuvo encendido durante 49.7 días, este vuelve a cero. Un poco extraño, pero en uno de los proyectos en que estoy trabajando ha sucedido :D.

No tan extraño, de hecho está documentado y es que el valor de GetTickCount se guarda en una variable de 32 bits. En mi infinita ocisosidad :D ya hice las cuentas y, en efecto sólo caben 49.7 días.

Por cierto Federico, ¿podrías editar tu mensaje? Ya no cabe en el ancho de mi pantalla.

// Saludos

delphi.com.ar 06-07-2004 18:02:27

Cita:

Empezado por roman
Bueno, pero, básicamente es mi while Result = 0 do (creo :confused: ).

Ahhh... y si el proceso ejecutado no es el correcto o una versión diferente??.. :D

Para comparar, yo subí el ejemplo que tenía armado a mi página, no recuerdo para quien lo había hecho, me llama la atención que no lo haya puesto en el foro anteriormente.
El ejemplo hace otra cosa, pero creo que es muy aplicable a este caso.

Saludos!

roman 06-07-2004 18:14:09

Cita:

Empezado por delphi.com.ar
Ahhh... y si el proceso ejecutado no es el correcto o una versión diferente??

¿Cómo no va a ser el proceso correcto si yo lo mando llamar?

De cualquier forma, mi apunte del while Result = 0 do no iba encaminado a decir que estábamos haciendo lo mismo, sino, esencialmente lo mismo y que hasta donde veo persiste el problema de que las llamadas a otras funciones como MakeAppTopMost no sutan efecto.

¡Todo esto por el block de notas! :D

delphi.com.ar 06-07-2004 18:18:38

Cita:

Empezado por roman
¿Cómo no va a ser el proceso correcto si yo lo mando llamar?

Nooo... supongamos que es una aplicación que distribuyes por la red, la pueden bajar usuarios con plataformas diferentes, con versiones y con "customizaciones" diferentes. Digamos que hasta pueden haber sobreescrito el Block de Notas, algo atípico pero estoy seguro de mas de un caso de esto.

¡¡Es para molestar un poco!! :D

roman 06-07-2004 18:22:10

Bueno, pero con tal paranoia mejor no enciendo mi PC. :D

Además estarás de acuerdo que podemos crear una aplicación que no sólo se llame notepad.exe sino que además la clase de su ventana principal se llame NOTEPAD.

Por otra parte, casi cualquier cosa que sustituya al note pad será mejor que tan infame aplicación :D :D

// Saludos


La franja horaria es GMT +2. Ahora son las 09:22:05.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi