Solved

control external app from delphi

Posted on 2002-03-26
21
1,481 Views
Last Modified: 2012-05-04
I would like to launch an external application and
have it minimize on my delphi form, similar to when
you iconize an app on the Window's task bar.
 
I am able to launch the external app, but I want it to
open minimized and have my form as its parent?

How can I do this? Can you please provide an example

Thanks,
Michael

0
Comment
Question by:mmcgurl
  • 5
  • 3
  • 3
  • +8
21 Comments
 
LVL 9

Expert Comment

by:ginsonic
ID: 6897147
listening
0
 
LVL 5

Expert Comment

by:alanwhincup
ID: 6897270
I'm not sure if this will work as i wrote it from my head and i can't test it but it may do:

function TForm1.ExecAppMinimized(FileName, Params : string) : Boolean;
var
  H, J : hWnd;
begin
  H := ShellExecute(Application.Handle, 'Open', PChar(FileName), PChar(Params), nil, SW_MINIMIZE);
  if H <> 0 then
  begin
    Windows.SetParent(H, Self.Handle);
    with ClientRect do
      SetWindowPos(H, 0, Left, Top, Right - Left, Bottom - Top, SWP_SHOWWINDOW);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ExecAppMinimized('notepad.exe', '');
end;

Cheers,

Alan
0
 

Author Comment

by:mmcgurl
ID: 6897370
Alan, I tested with your example. Notepad.exe instance minimized to the windows task bar but not to my form (form1). It is critical that the external app minimize
to my form.


Thanks, Michael
0
 
LVL 6

Expert Comment

by:Stuart_Johnson
ID: 6897760
Hi Michael,

I'm really curious about this, and I guess I'm trying to clarify what you're asking more than anything else.

What you're trying to achieve (if I'm correct in understanding your question) is to allow externally launched applications to become child windows of your application (similar to MDI).  And, when you minimize the application, instead of it minimizing to the task bar, it minimizes to iconic form in the bottom of your window.

If that's correct, then this is going to be very difficult.  You'd need to somehow hook the new applications minimize message and then hide the window completely (obviously allow it to still show in the task manager), and show an iconized representation of that window in your application.

The last part won't be that hard to do.  Hiding a window is easy, as is creating an button at the bottom of your form with the same icon as the launched EXE.  Trapping that Minimize message is going to be the tricky part.

I'll be watching this thread with great interest.

Best of luck,

Stu
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6898611
Stu's idea of hiding the application and drawing the minimized version of it yourself it a good one.

Generally you can't set the parent of a window to a "foreign" window. I mean, parent and child must be in the same process. That's Windows system design. If the other application supports OLE, you might be able to do the things you want, not sure about minimizing, though. Apart from OLE I guess that Stu's solution is the best/only way to go...

Regards, Madshi.

P.S: The explorer paints the minimized version itself, too. The buttons in the taskbar are definately not windows that belong to the process they represent.
0
 
LVL 2

Expert Comment

by:isstorr
ID: 6898746
listening
0
 
LVL 6

Accepted Solution

by:
Stuart_Johnson earned 100 total points
ID: 6898825
Madshi,

I thought you'd show up in this one :)

Is there anyway possible that you know of to capture a minimize?  I would imagine minimize/maximize/restore are all local messages (to the app - not broadcasted).

I guess the next suggestion could be - if the host application starts the applications, couldn't we monitor each spawned process and watch for a change in the window visibility?

This is sounding like that question I asked about a couple of weeks ago (the Morpheus one).

Still thinking about it.

Stu
0
 
LVL 20

Assisted Solution

by:Madshi
Madshi earned 100 total points
ID: 6898832
Well, you should be able to get the minimize messages by using a message hook (SetWindowsHookEx). You could also try to poll, but that's ugly, time consuming, and probably it will not look good.

Regards, Madshi.
0
 
LVL 6

Expert Comment

by:Stuart_Johnson
ID: 6898835
OK.  Here's a thought.....

(Execution process) :

You want to run Notepad from your app:

   1.  First, get the icon (or associated icon) - convert to bitmap and store.
   2.  Launch the application - store the process handle
   3.  Immediately after launch, remove all refrence to the application from the task bar
   4.  Create a button on the bottom of your window with the app's name and icon drawn on it (you already have the bitmap).
   5.  Monitor the processes, and if the spawned process is terminated (by any means), free the process handle (there you go, Madshi!), and delete the button.

What this means is, you have an application which "appears" to be run from your application, but it's not really.  If the application is minimized, it just becomes hidden.  The buttons in your application could just have Minimize, Maximize and Close always enabled (in otherwords, totally stateless).

Madshi, Inthe and I worked on some code a few weeks ago which was a very basic task manager/process monitor.  This code will give you almost everything you need except the button drawing (it even extracts the icon and creates a bitmap from it for you).  I'm quite happy to give you this entire project to start you off.

I'm not sure off hand how to hide an app from the task bar, but I know I have code for it somewhere (or someone else will be bound to post it).  It just has to show the app in the task manager (you still need to allow Windows to have control over the spawned applications incase it/they crash).

Does that make sense to anyone, or am I speaking (typing) complete and utter bollocks?

Stu


0
 
LVL 6

Expert Comment

by:Stuart_Johnson
ID: 6898850
Madshi,

> You could also try to poll, but that's ugly, time
> consuming, and probably it will not look good.
Pft!  That's all very well for you geniuses to say :)  So, how would you propose to do this?  I like the hook idea, but would that work do you think?

Stu
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 20

Expert Comment

by:Madshi
ID: 6899046
Yes, it would work...   :-)   No time to prove it, though...
0
 
LVL 3

Expert Comment

by:smurff
ID: 6899465
listening....
0
 
LVL 1

Expert Comment

by:gd2000
ID: 6899466
Stu,

Just reading the thread myself and I'd be very interested in getting my hands on that code that yourself, Madshi and Inthe worked on. Do you have a web link to it?

Thanks,
0
 
LVL 6

Expert Comment

by:Stuart_Johnson
ID: 6900457
No.  I don't.  But I'll be more than happy to email it too you if you leave me an email address.

Just be warned, it's pretty messy and there's not a lot of comments.  It was pulled together and changed so many times to get Morpheus to work (which it never ended up doing which doesn't matter cause Morpheus is crap now anyway).

Stu
0
 
LVL 1

Expert Comment

by:gd2000
ID: 6902013
Thanks Stu -

Could you mail it to the address gd2000@dol.ie

Thanks,

Gary
0
 
LVL 17

Expert Comment

by:geobul
ID: 6902326
spying...
0
 

Author Comment

by:mmcgurl
ID: 6902544
Stu,

 Your comments sound very interesting ... can you send
 me that code you mentioned. My email address is
 michael.j.mcgurl@intel.com

Thanks,
0
 
LVL 17

Assisted Solution

by:geobul
geobul earned 100 total points
ID: 6920543
Hi,
It's a project containing one form, one additional unit and one thread-specific hook in a dll.

unit 1: on the form there are:
- a button to launch the external app (notepad)
- a popup menu (restore, minimize, close)
- an image (with the popup menu above) representing the notepad instance.
--------
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Menus, ExtCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    imgIcon: TImage;
    PopupMenu1: TPopupMenu;
    Restore1: TMenuItem;
    Minimize1: TMenuItem;
    Close1: TMenuItem;
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Restore1Click(Sender: TObject);
    procedure Minimize1Click(Sender: TObject);
    procedure Close1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}
uses unit2;

// Functions prototypes for the hook dll
type
 TStartTheHook = procedure(TheThreadId:DWORD); stdcall;
 TStopTheHook = procedure; stdcall;

var
  hHookLib : THANDLE; // A handle to the hook dll
  StartTheHook : TStartTheHook; // Function pointer
  StopTheHook : TStopTheHook; // Function pointer
  LibLoadSuccess : boolean = false; // If the hook lib was successfully loaded
  TheMessage: cardinal; // custom windows message to communicate with the dll

// Application.OnMessage
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  // our registered custom message
  if (Msg.Message = TheMessage) then begin
    // close
    if (Msg.wParam = SC_CLOSE) then begin
      StopTheHook;
      imgIcon.Visible := false;
      Handled := True;
    end;
    // minimize
    if (Msg.wParam = SC_MINIMIZE) then begin
      ShowWindow(H, SW_HIDE);
      Handled := True;
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // register our message
  TheMessage := RegisterWindowMessage(PChar('geobul'));
  Application.OnMessage := AppMessage;
  @StartTheHook := nil;
  // load dll
  @StopTheHook:= nil;
  hHookLib := LoadLibrary('THEHOOK.DLL');
  if hHookLib <> 0 then begin
    @StartTheHook := GetProcAddress(hHookLib, 'StartTheHook');
    @StopTheHook := GetProcAddress(hHookLib, 'StopTheHook');
    if ((@StartTheHook <> nil) and (@StopTheHook <> nil)) then begin
      LibLoadSuccess := true;
    end;
  end else begin
    // load failed
    FreeLibrary(hHookLib);
    hHookLib := 0;
    @StartTheHook := nil;
    @StopTheHook := nil;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // Start the app and the hook
  if LibLoadSuccess then begin
    if not imgIcon.Visible then begin
      // start the external app
      ExecApplication('c:\windows\notepad.exe','',SW_HIDE);
      if PID > 0 then begin
        Sleep(500);
        // get its main window handle
        GetWindowHandle;
        if IsWindow(H) then begin
          // start the hook
          StartTheHook(TID);
          // show the icon
          imgIcon.Top := Form1.ClientHeight - imgIcon.Height - 10;
          imgIcon.Left := 10;
          imgIcon.Visible := true;
        end;
      end;
    end;
  end;
end;

// popup menu Restore
procedure TForm1.Restore1Click(Sender: TObject);
begin
  ShowWindow(H, SW_SHOWNORMAL);
end;

// popup menu Minimize
procedure TForm1.Minimize1Click(Sender: TObject);
begin
  ShowWindow(H, SW_HIDE);
end;

// popup menu Close
procedure TForm1.Close1Click(Sender: TObject);
begin
  StopTheHook;
  PostMessage(H, WM_CLOSE, 0, 0);
  imgIcon.Visible := false;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // Free the hook dll
  if (LibLoadSuccess = TRUE) then begin
    if imgIcon.visible then StopTheHook;
    FreeLibrary(hHookLib);
  end;
  // close the app if it's still running
  if IsWindow(H) then PostMessage(H, WM_CLOSE, 0, 0);
end;

end.
-------
unit Unit2;
interface
uses
  Windows;

procedure ExecApplication(APPName, CmdLine: String; ShowMode: DWord);
procedure GetWindowHandle;

var
  PID, TID: DWord; // ProcessID and ThreadID of the external app
  H: HWND;

implementation

// run external app
// set PID=ProcessID and TID=ThreadID
procedure ExecApplication(APPName, CmdLine: String; ShowMode: DWord);
var StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
begin
 try
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb:=SizeOf(StartInfo);
  StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
  StartInfo.wShowWindow:=ShowMode;
  if AppName<>'' then CreateProcess(PChar(APPName), PChar(CmdLine), nil, nil, False, 0,
                         nil, nil, StartInfo, ProcInfo)
  else CreateProcess(nil, PChar(CmdLine), nil, nil, False, 0,
                         nil, nil, StartInfo, ProcInfo);
   PID := ProcInfo.dwProcessId;
   TID := ProcInfo.dwThreadId;
 finally
  CloseHandle(ProcInfo.hProcess);
  CloseHandle(ProcInfo.hThread );
 end;
end;

function EnumerateWindows(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  ProcID: DWORD;
begin
  result := true;
  GetWindowThreadProcessId(hWnd, @ProcID);
  // Pids are equal
  if ProcID = PID then begin
    H := hWnd;
    result := false;
  end;
end;

// enumerate windows to find Main window handle which will be assigned to H
procedure GetWindowHandle;
begin
  EnumWindows(@EnumerateWindows, 0);
end;

end.
------
and the hook dll:
-------
library TheHook;

uses
  Windows,
  Messages,
  SysUtils,
  Dialogs;

var
  TheHookHandle : HHOOK;
  TheMessage: cardinal;

function TheHookProc(Code : integer; wParam : DWORD; lParam : DWORD): longint; stdcall;
var
  h: HWND;
begin
  result := 0;
  if (Code = HCBT_SYSCOMMAND) then begin
    // find calling app's window
    // !!! Replace 'Form1' with the title of your main window
    h := FindWindow(nil,'Form1');
    if (wParam and $F060) = $F060 then begin // SC_CLOSE
      if IsWindow(h) then begin
        if not PostMessage(h, TheMessage, SC_CLOSE, 0) then ShowMessage('Error in post');
      end else begin
        ShowMessage('Calling app not found')
      end;
    end;
    if (wParam and $F020) = $F020 then begin // SC_MINIMIZE
      if IsWindow(h) then begin
        if not PostMessage(h, TheMessage, SC_MINIMIZE, 0) then ShowMessage('Error in post');
      end else begin
        ShowMessage('Calling app not found')
      end;
    end;
  end;
  {Call the next hook in the hook chain}
  if (Code < 0) then
    result := CallNextHookEx(TheHookHandle, Code, wParam, lParam);
end;

procedure StartTheHook(TheThreadID: DWORD); stdcall;
begin
  if (TheHookHandle = 0) then begin
    TheHookHandle := SetWindowsHookEx(WH_CBT, @TheHookProc, hInstance, TheThreadID);
  end;
end;

procedure StopTheHook; stdcall;
begin
  if (TheHookHandle <> 0) then begin
    // Remove our hook and clear our hook handle
    if (UnhookWindowsHookEx(TheHookHandle) <> FALSE) then begin
      TheHookHandle := 0;
    end;
  end;
end;

exports
  StartTheHook,
  StopTheHook;

begin
  // register the same message
  TheMessage := RegisterWindowMessage(PChar('geobul'));
end.
------
Regards, Geo
0
 

Expert Comment

by:CleanupPing
ID: 9343214
mmcgurl:
This old question needs to be finalized -- accept an answer, split points, or get a refund.  For information on your options, please click here-> http:/help/closing.jsp#1
EXPERTS:
Post your closing recommendations!  No comment means you don't care.
0
 
LVL 17

Expert Comment

by:geobul
ID: 9360886
Split points: Stuart Johnson, Madshi and myself. (I only coded their ideas).
0
 
LVL 7

Expert Comment

by:knightmad
ID: 9471021
Wow, Its been a long time since this comment have been visited!

mmcgurl,
No comment has been added lately (16 days), so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area for this question:

RECOMMENDATION: split points between Stuart Johnson http:#6897760 and Madshi http:#6898611 and geobul http:#9360886

-- Please DO NOT accept this comment as an answer ! --

Thanks,

knightmad
EE Cleanup Volunteer

Remember you (all) can object this recommendation if you disagree, just leave a post here explaining why are you objecting and what should be done instead.
Within 7 days probably this thread will be closed, so, hurry up! : )
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…

759 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now