[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 2818
  • Last Modified:

Get the handle of a windows application that was launched using ShellExecute

I want to get the handle of an windows application which was launched using ShellExecute so that later I can use the SetForegroundWindow to show it again instead of launching another instance. I dont want to use the GetWindowText api call to get the handle of that aplpication. I know there is an instance handle that gets returned but it isn't the same as the actual handle of the application. Just note I am talking about launching other windows appliactions which are not delphi apps i.e. like notepad for instance. So I want to launch notepad using ShellExecute get the handle for that instance of notepad, use SetForegroundWindow to show that instance of Notepad without searching the desktop windows for a title of 'notepad' to get the handle. How do I do this? Thanx in advance!
0
skynergy
Asked:
skynergy
  • 4
  • 4
  • 3
  • +2
4 Solutions
 
ZhaawZSoftware DeveloperCommented:
What you need is a handle of a *window*, not an application (application may have many windows, and each window/control has its own handle). I believe it could be a bit difficult to determine which is the right handle (i.e., handle of the main window of an application).
GetWindowText() has nothing to do with getting handle ;] at least it *should not* have...
0
 
pcsentinelCommented:
Don't think you can, suggest you use create process and some other funcs that I've listed below

************************UNIT CODE****************************
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    fPid: DWord;
    function LaunchApp(const App, CmdLine, WrkDir: string): DWord;
    function GetMainAppWndFromPid(PID: DWORD): HWND;
    function IsMainAppWindow(Wnd: HWND): Boolean;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

function TForm1.IsMainAppWindow(Wnd: HWND): Boolean;
var
  lParentWnd: HWND;
  lExStyle: DWORD;
begin
  if IsWindowVisible(Wnd) then
  begin
    lParentWnd := GetWindowLong(Wnd, GWL_HWNDPARENT);
    lExStyle := GetWindowLong(Wnd, GWL_EXSTYLE);
    Result := ((lParentWnd = 0) or (lParentWnd = GetDesktopWindow)) and
      ((lExStyle and WS_EX_TOOLWINDOW = 0) or (lExStyle and WS_EX_APPWINDOW <> 0));
  end
  else
    Result := False;
end;

function TForm1.GetMainAppWndFromPid(PID: DWORD): HWND;
type
  PSearch = ^TSearch;
  TSearch = record
    PID: DWORD;
    Wnd: HWND;
  end;
var
  lSearchRec: TSearch;

  function EnumWindowsProc(Wnd: HWND; Res: PSearch): Boolean; stdcall;
  var
    lWindowPid: DWORD;
  begin
    lWindowPid := 0;
    GetWindowThreadProcessId(Wnd, @lWindowPid);
    if (lWindowPid = Res^.PID) and IsMainAppWindow(Wnd) then
    begin
      Res^.Wnd := Wnd;
      Result := False;
    end
    else
      Result := True;
  end;

begin
  lSearchRec.PID := PID;
  lSearchRec.Wnd := 0;
  EnumWindows(@EnumWindowsProc, Integer(@lSearchRec));
  Result := lSearchRec.Wnd;
end;

function TForm1.LaunchApp(const App,CmdLine,WrkDir: string): DWord;
//launches the prrocess ProcNum
var
      lStartupInfo: TStartupInfo;
      lProcessInfo: TProcessInformation;
      lSuccess: boolean;
      lWidthDiv: integer;
      lHeightDiv: integer;
      lShowX: byte;
      lShowY: byte;
  lUser: string;
begin
      try
            FillChar(lStartupInfo, SizeOf(TStartupInfo), #0);
            lStartupInfo.lpDesktop:=pChar('winsta0\default');
            lStartupInfo.cb := SizeOf(TStartupInfo);
            lStartupInfo.dwFlags := STARTF_USESHOWWINDOW;
            lStartupInfo.wShowWindow := SW_SHOW;
            lSuccess := CreateProcess(nil, PChar(App+CmdLine), nil, nil, False,
                  NORMAL_PRIORITY_CLASS, nil, PChar(WrkDir), lStartupInfo, lProcessInfo);
            if lSuccess then
            begin
                  WaitForInputIdle(lProcessInfo.hProcess, INFINITE);
                  CloseHandle(lProcessInfo.hThread);
                  CloseHandle(lProcessInfo.hProcess);
                  Result:=lProcessInfo.dwProcessId;
            end
            else
                  Result:=0;
      except
            Messagedlg('Error launching Application',mtError,[mbok],0);
            Result:=0;
      end;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
      fPID:=LaunchApp('c:\windows\notepad.exe','','c:\Windows');
end;

procedure TForm1.Button2Click(Sender: TObject);
var
      lStartupInfo: TStartupInfo;
begin
  ShowWindow(GetMainAppWndFromPid(fPID),SW_SHOW);
end;

end.

****************************FORM CODE********************
object Form1: TForm1
  Left = 120
  Top = 60
  Width = 374
  Height = 191
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 128
    Top = 72
    Width = 75
    Height = 25
    Caption = 'Launch'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 128
    Top = 104
    Width = 75
    Height = 25
    Caption = 'BringToFront'
    TabOrder = 1
    OnClick = Button2Click
  end
end
*******************************************************

regards (some code form JCL)
0
 
tobjectpascalCommented:
unit ThreadtoWnd;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  WindowList: TList;

implementation

{$R *.DFM}

function GetWindow (Handle: HWND; LParam: longint): bool; stdcall;
begin
  Result := true;
  WindowList.Add (Pointer(Handle));
end;

Function GetHandles(ThreadID: Cardinal): Hwnd;
var i: integer;
    wnd,Hnd: HWND;
    Buffer: array [0..255] of char;
    ProcessID,CPid: DWord;
begin
  Result:=0;
  WindowList := TList.Create;
  EnumWindows(@GetWindow,Wnd);
    for i := 0 to WindowList.Count - 1 do
     begin
      Hnd := HWND (WindowList [i]);
      GetWindowText (Hnd, Buffer, SizeOf (Buffer) - 1);
      getclassname(hnd,buffer,sizeof(buffer));
      GetWindowThreadProcessID(hnd,@cpid);
//      Form1.Memo1.Lines.Add('Handle '+IntToStr(Hnd)+' Text: '+Buffer+' TH '+IntToStr(ProcessID));
      If ThreadID=CPID Then
       Begin
        Result:=Hnd;
        Form1.Memo1.Lines.Add('Handle '+IntToStr(Hnd)+' Text: '+Buffer+' TH '+IntToStr(ProcessID));
        Exit;
      End;
     end;
End;

Function ExecApplication(APPName, CmdLine: String): HWnd;
var
  StartInfo: TStartupInfo;                    ProcInfo: TProcessInformation;
begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb:=SizeOf(StartInfo);
  StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
  StartInfo.wShowWindow:=SW_Show;
  if AppName<>'' then
     CreateProcess(PChar(APPName), PChar(CmdLine), nil, nil, False, 0, nil, nil, StartInfo, ProcInfo);
   Sleep(1000);
  Result:=GetHandles(ProcInfo.dwProcessId);
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread );
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
 Wnd: Hwnd;
 N: Integer;
begin
 Wnd:=ExecApplication('c:\winnt\notepad.exe','');
 If Wnd<>0 Then // if not 0 then valid handle
  Begin
   ShowWindow(Wnd,sw_hide);
   Sleep(1000);
   ShowWindow(Wnd,SW_Show);
  End;  //make the window do something
end;

end.
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
CodedKCommented:
When you use ShellExecute IF you using it with (sw_show)...
You could use GetForegroundWindow which will return a handle for the focused window.

You said :
>> I know there is an instance handle that gets returned but it isn't the same as the actual handle of the application.

This could happen for many reasons.
Maybe this is an ActiveX control..or
It's not mandatory in Windows that a process has to have a window ... or
As ZhaawZ said an application may have many windows.

You could use WindowFromPoint and use the mouse to get the handle of every window in the application.
0
 
tobjectpascalCommented:
well CreateProcess launches the program returning the PID, not the window handle, then you enum all the windows then convert the window handle to a PID and then compare it to the PID which got returned by createprocess, when you find a match the handle must be that..

0
 
skynergyAuthor Commented:
Thank you for all the feedback guys. Give me some time to test your suggestions.

I have tried Pcsentinel's code but it doesn't bring the opened notepad to the front. Am I missing something?
0
 
CodedKCommented:
I think the problem in this question is the class.

Asker is saying that he doesnt want to use FindWindow because he doesnt get the classname right.
The problem is that you are going to get the same class name you got with FindWindow what ever you try...
Maybe this is an ActiveX menu..

Please try this application :
http://www.dennisbabkin.com/php/download.php?what=WinID
0
 
tobjectpascalCommented:
unit ThreadtoWnd;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  WindowList: TList;

implementation

{$R *.DFM}

function GetWindow (Handle: HWND; LParam: longint): bool; stdcall;
begin
  Result := true;
  WindowList.Add (Pointer(Handle));
end;

Function GetHandles(ThreadID: Cardinal): Hwnd;
var i: integer;
    wnd,Hnd: HWND;
    Buffer: array [0..255] of char;
    ProcessID,CPid: DWord;
begin
  Result:=0;
  WindowList := TList.Create;
  EnumWindows(@GetWindow,Wnd);
    for i := 0 to WindowList.Count - 1 do
     begin
      Hnd := HWND (WindowList [i]);
      GetWindowText (Hnd, Buffer, SizeOf (Buffer) - 1);
      getclassname(hnd,buffer,sizeof(buffer));
      GetWindowThreadProcessID(hnd,@cpid);
      If ThreadID=CPID Then
       Begin
        Result:=Hnd;
        Form1.Memo1.Lines.Add('Handle '+IntToStr(Hnd)+' Text: '+Buffer+' TH '+IntToStr(ProcessID));
        Exit;
      End;
     end;
End;

Function ExecApplication(APPName, CmdLine: String): HWnd;
var
  StartInfo: TStartupInfo;                    ProcInfo: TProcessInformation;
begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb:=SizeOf(StartInfo);
  StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
  StartInfo.wShowWindow:=SW_Show;
  if AppName<>'' then
     CreateProcess(PChar(APPName), PChar(CmdLine), nil, nil, False, 0, nil, nil, StartInfo, ProcInfo);
 Sleep(2000);  //give notepad time to run (2 seconds)
  Result:=GetHandles(ProcInfo.dwProcessId);
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread );
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
 Wnd: Hwnd;
 N: Integer;
begin
 Wnd:=ExecApplication('c:\winnt\notepad.exe','');
 If Wnd<>0 Then
  Begin
   Sleep(3000); //pause for 3 seconds while you move notepad to the back
   SetWindowPos(Wnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE Or SWP_NOSIZE);
  End;
end;

end.


The only modification made is to bring it to the front using SetWindowPos, this should do exatly as you requested now.
0
 
pcsentinelCommented:
Sorry , the showwindow will bring the window back from a minimised state,

add BringWindowToFront as in

  ShowWindow(GetMainAppWndFromPid(fPID),SW_SHOW);
  BringWindowToTop(GetMainAppWndFromPid(fPID));


regards
0
 
skynergyAuthor Commented:
Great! Both (pcsentinel & tobjectpascal) examples works very well and thank you for the help so far. To CodedK & ZhaawZ in my question I referred to the Notepad application as both a window and application, maybe not the same thing as a application can have more than one window but in the case of the notepad it is. Sorry if my windows api knowledge is a bit limited so I apologize if I dont use the correct terminology. I do know how to get the handle on a window by searching for its title text. My question was more directed to get the handle of the window when I launch it so that I dont have to go and search for the title of that window. I also dont want the user of the application to specify the title of the window, it was an option but I would prefer not to. Also I dont really want to use GetForegroundWindow to get the handle of the window when I used ShellExecute with swShow because I might be launching two or more different windows in succession which would make this difficult. I do appreciate the link for WinID thank you!

Just another question. If Notepad was launched and then minimised, how can I show it using the two examples and make the foreground window. I tried:
ShowWindow(hwnd, SW_SHOWDEFAULT);
BringWindowToTop(hwnd);
but this will show it but not select it.

Thank you!
0
 
ZhaawZSoftware DeveloperCommented:
for restoring minimised window:
if GetWindowLong(hwnd, gwl_style) and ws_iconic <> 0 then ShowWindow(hwnd, sw_restore);
0
 
skynergyAuthor Commented:
Thanx this still restores the window but doesn't give it focus.
0
 
pcsentinelCommented:
 ShowWindow(GetMainAppWndFromPid(fPID),SW_SHOW);
  BringWindowToTop(GetMainAppWndFromPid(fPID));
  SetActiveWindow(GetMainAppWndFromPid(fPID));

regards
0
 
ZhaawZSoftware DeveloperCommented:
pcsentinel, SetActiveWindow() works only within a thread

var
  wnd : cardinal;
begin
// store handle of window in a variable to avoid calling function multiple times
wnd := GetMainAppWndFromPid(fPID);
// check if it is minimised; restore, if minimised
if GetWindowLong(wwnd, gwl_style) and ws_iconic <> 0 then ShowWindow(hwnd, sw_restore);
// bring window to front of other windows and activate it
SetForegroundWindow(wnd);
end;
0
 
pcsentinelCommented:
ZhaawZ, the code above combined with my previous code works fine
0
 
skynergyAuthor Commented:
Thank you for all the help guys, I really appreciate it!
0

Featured Post

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

  • 4
  • 4
  • 3
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now