[Webinar] Streamline your web hosting managementRegister Today

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

multiple applications

In our system we have a mainmenu app and many modular executables coming from it. The entire system is very large and changes regularly so it is easiest to develop it in this way.

The modular apps are run from the mainmenu by clicking on a dbgrid. If the user clicks twice (by accident) the first click runs the app and the second gives focus back to the mainmenu app.

When the modular app loads it loads in front because its form is set to fsstayontop (the app must run on top) but does not have focus.

When a modular app exits it sends a message to the main menu telling it to activate itself.

How can I make the mainmenu not receive any more mouse clicks or keyboard inputs or anything that would make it active again after they have chosen to run a program from it?

Best case scenario would be to have a flag set when a program is run so that the mainmenu app won't become active but I have not been successful in implementing this or overriding the appropriate messages.

Even on assigning a message handler to an application and setting handled to true for every message it overrides every message EXCEPT when the app is not active and you click on it, it does become active???

This has been the thorn in my side for about 2 years now and remains the only system issue. Please help :)
0
Smegboy
Asked:
Smegboy
  • 16
  • 12
  • +3
1 Solution
 
MadshiCommented:
I have two ideas:

(1) In the FormPaint method of all modular executables, you could do this:

var FirstFormPaint : boolean = true;

procedure TForm1.FormPaint(Sender: TObject);
begin
  if FirstFormPaint then begin
    FirstFormPaint := false;
    SetForegroundWindow(Handle);
  end;
  ...
end;

(2) After starting the executable, do this:

  ShellExecute(...);
  YourMainAppsMainForm.Enabled := false;

Then, when you get the message from the modular executable, that it has finished (BTW, you could use WaitForSingleObject(modularProcessHandle)), you must set the Enabled property to true again...

Does this help?

Regards, Madshi.
0
 
Fatman121898Commented:
Hi Smegboy,

I had similar problem a few weeks ago - how to avoid multiple instantions of my app.

In the body of the program I put a code wich detects if the main form already exists, and if so - terminates second instantion:

program MyProg;

uses
 ...

begin
  if (FindWindow('TMainForm', nil)>0)
    then
      begin
        ShowMessage('Application already running!');
        Application.Terminate;
      end
    else
      begin
        Application.Initialize;
        Application.CreateForm(TMainForm, MainForm);
        //create other forms etc.
        Application.Run;
     end;
end.

This was the solution of my problem, maybe yours - too :-)

Jo.
0
 
AttarSoftwareCommented:
The way we do it, is we create the new process with CreateProcess, and then set the main app.enabled to false, and then call WaitForSingleObjectEx, checking for the spawned process to terminate, before setting the main app.Enabled back to true and continuing from where we left off...

Hope this helps, I can dig out some source code if you need it...

Tim.
0
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.

 
philipleighsCommented:
Hi,

Maybe this will help you.

The ExecuteExclusiveProcess function starts an exe.
It disables your main form.
It waits for the exe to finish (using a different thread)
While that's going on, it is still handling messages (like Paint) with calls to Application.HandleMessage.
When the exe finishes, it enables your main form, and brings it to the front.

Cheers,
Phil.

>>>>>>>>>>>>>
unit execute;

interface

{ Create a process and wait until it completes before continuing with
  current process }
function ExecuteExclusiveProcess(CmdLine : String) : boolean;



implementation
uses
  Forms,
  classes,
  Windows;

var
  ProcessInfo : TProcessInformation;

type
  TMyThread = class (TThread)
    procedure execute; override;
    end;

procedure TMyThread.execute;
begin
  FreeOnTerminate := True;
  WaitForSingleObject(ProcessInfo.hprocess, INFINITE);
  Application.Mainform.enabled := true;
end;


function ExecuteExclusiveProcess(CmdLine : String) : boolean;
{ Create a process and wait until it completes before continuing with
  current process }
var
  StartUpInfo : TStartupInfo;
begin
  Result := False;
  Fillchar(StartUpInfo, sizeof(StartUpInfo), 0);
  With StartUpInfo do
    begin
    cb := sizeof(StartUpInfo);
    dwflags := STARTF_USESHOWWINDOW;
    wShowWindow := SW_SHOWNORMAL;
    end;
  if CreateProcess(nil,
                   pchar(CmdLine),
                   nil,
                   nil,
                   false,
                   0,
                   nil,
                   nil,
                   StartUpInfo,
                   ProcessInfo) then
    begin
    Result := True;
    Application.MainForm.enabled := false;
    WaitForInputIdle(ProcessInfo.hprocess, INFINITE);
    ShowStatusMessage('');
    TMyThread.Create(False);
    repeat
      Application.HandleMessage;
    until Application.MainForm.enabled;
    SetForegroundWindow(Application.handle);
    end;
end;


end.
<<<<<<<<<<
0
 
SmegboyAuthor Commented:
Thanks so much to the people who responded, your help has been much appreciated.

I think I will use that execute unit over what I am currently using, Thankyou Philip.

The only problem I have now is although the mainmenu app runs the application and although the mainactive becomes inactive while the other program is running, you can still lose focus from the modular application by clicking on the mainmenu application.

Does anyone have any ideas on how to stop this one.

Cheers,

Matt.

0
 
philipleighsCommented:
Hi again,

Hmm, you could add a handler for Application.OnActivate. If you have a child exe running, then activate that child instead? You'd have to get it's window handle, and use SetForegroundWindow.
That's a bit nasty and things might flicker a bit.

Or prehaps you could capture WM_WindowPosChanging and see if the Z order is changing. If the Z order will cause your main app to cover the other one, then set Msg.Result to 1 (or whatever) and don't call inherited. That will stop the window manager from processing the message.
That's a bit nasty too, but it's the best I can think of right now, sorry.

Cheers,
Phil.
0
 
SmegboyAuthor Commented:
I have tried the application.onactivate one before but for some reason I think having something to do with still having the mouse down on the mainmenu app, it never actually loses/gains focus, the child app just loads without it so onactivate for mainmenu is never called?

I have tried using WM_ACTIVATE and if its value is WA_INACTIVE then setforegroundwindow.. this doesn't completely work I think because the message is sent when the mouse button is pressed down while outside the app, when the mousebutton is on its way up again it doesn't deactivate it but leaves the edit controls without focus.
Any ideas?

Next port of call was to try and intercept WM_KILLFOCUS but have had no luck with this.

Any more thoughts?

Cheers.
0
 
SmegboyAuthor Commented:
I have also tried intercepting WM_ACTIVATE and WM_ACTIVATE APP using
similar methods to the following,

if message.wparam=0{WA_INACTIVE} then begin
        caption:='fred';
        //setforegroundwindow(form1.handle);
        //sendmessage(form1.handle,WM_SHOWWINDOW,0,0);
        showwindow(form1.handle,SW_SHOWNORMAL);
     end else inherited;

something must be called after receiving these messages that deactivates the app.
0
 
philipleighsCommented:
Did you look at WM_WINDOWPOSCHANGING?
0
 
SmegboyAuthor Commented:
Yes I did, but there was something wrong there too.. Hey Philip, ust read your info. Your a kiwi, where are you based?

0
 
philipleighsCommented:
A kiwi yeah, I'm in Christchurch. Have you been here? Where are you from?

Cheers,
Phil.
0
 
SmegboyAuthor Commented:
A modal window cannot lose focus to its parent window, I know that you can make a window from another application your parent window, is there some way you can make the mainform of another app (the child app) modal?

Or is this dangerous?
0
 
SmegboyAuthor Commented:
Phil, I am a kiwi too based in Auckland.

Perhaps we could talk on ICQ or IRC?

0
 
philipleighsCommented:
>>is there some way you can make the mainform of another app (the child app) modal
<<

If you can compile your apps as DLLs then it is really easy.

Only the DPR has to change. That way you wouldn't see the second icon in the taskbar - the "exe" would seem exactly the same as if it was a modal dialog. Interested? I'll show you how if you want.

Yeah, sure I'd love to chat Matt. Esp if your company has Delphi contract work on offer. Drop a line to pleighs@hotmail.com (sorry no chat setup).

Cheers,
Phil.
0
 
SmegboyAuthor Commented:
Yes can you, not sure how it would effect the fact that some of our apps run around direct-x... mind you I think we have programmed them all in window mode now.

If you could show me how then that would be cool.
0
 
SmegboyAuthor Commented:
Also some of our apps are about 3-4mgs and 100s can be run in a night, how robust is the releasing of dlls from memory?
0
 
philipleighsCommented:
Say the dpr of one of the secondary apps looks like this:

>>>>>>>>>>>>>>>>>>
program Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
<<<<<<<<<<<<<<<<<<

Then make changes like this to convert it to a dll.

>>>>>>>>>>>>>>>>>>
library Project1;

uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

procedure Start(AppHandle: HWND); stdcall;
  var Form1: TForm1;
  begin
    Application.Handle := AppHandle;
    Form1 := TForm1.Create(Application);
    try
      Form1.ShowModal;
    finally
      Form1.Release;
      Application.Handle := 0;
    end;
  end;

exports
  Start;

begin
end.
<<<<<<<<<<<<<<<<


And in the main app, you had something like:
>>>>>>>>>>>>>>>>>>>
ExecuteExclusiveProcess('Project1.exe');
<<<<<<<<<<<<<<<<<<<

Change that to (may have typos):
>>>>>>>>>>>>>>>>>>>

type
  TStartProc = procedure(AppHandle: HWND); stdcall;

procedure StartProject1;
var DllHandle: HMODULE;
    StartProc: TStartProc;
begin
  DllHandle := LoadLibrary('Project1.dll');
  if DllHandle <> 0 then
    begin
      try
        StartProc = GetProcAddress(DllHandle, 'Start');
        if Assigned(StartProc) then
          begin
            StartProc(Application.Handle);
          end;
      finally
        FreeLibrary(DllHandle);
      end;
    end;
end;
<<<<<<<<<<<<<<<<<<<

This is not a nasty hack. It is the way that Borland says you should put dialogs in a dll.

It is robust. The freeing of the dll is in a finally block so it is called no matter what. But don't take my word for it. You'd have to try it out and see if it fits in with your system.

Cheers,
Phil.


0
 
SmegboyAuthor Commented:
Philip, I have been playing around with the whole dll process, seems quite good. Only problem I have so far is when I drop one of my visual components onto a project and compile it as a dll it throws access violation errors.

Obviously something I am doing wrong within the code for that component.

Are there limitations to dlls? There are probably things like this that always pop up when converting from an app to dll, are there common probs?

Cheers.
0
 
SmegboyAuthor Commented:
Are there any problems with calling a dll from within another dll?
0
 
philipleighsCommented:
Hi Matt,

>>
Are there any problems with calling a dll from within another dll
<<
No, this is reasonably common.

As far as the AV, the major difference is that the DLL is using the Application object of the parent exe. It doesn't have it's own Application object.
That means that Application.MainForm is your form with the main menu bar.

If you can track the AV down to a few lines of code, then paste them here if you like and I'll see if I can help.

By the way, do you know how to debug the dll? Open the dll project, click Run Parameters and type the exe into the Host Application box. Now you can "run" the dll and stop at breakpoints in the dll code.

Cheers,
Phil.
0
 
SmegboyAuthor Commented:
Phil, thanks for your help,

I have narrowed the problem down to a call in one of my visual components (I think based on a timage class). The call is

procedure cgetback(a:integer;b:tcolor;c:string);
var v:variant;
begin
           v:=createoleobject('mjc.nwobject');
   //v.getback(a,b,c);
   v:=unassigned;

The image calls a dll procedure and passes its handle to it so that the dll can be drawn, the important thing to note hear is that the call is not even being made, its the createoleobject call that is throwing the error.

Cheers.
0
 
SmegboyAuthor Commented:
I just found out that if I take the call out of the constructor and move it to a button, it throws an error

coinitialize was not called?

Does this help.
0
 
philipleighsCommented:
Hmm, I hadn't thought about COM.

CoInitialize needs to be called once for each thread. You DLL is now in the same thread as the parent exe. So I guess you should add ComObj (or is it ComServ) to the DPR of the parent exe?

Is the 'mjc.nwobject' class something that was in one of your sub-exes? If so then another possibility is that the 'mjc.nwobject' class is now exposed from a dll instead. If so, then the CLSID in the registry probably only knows about a LocalServer32, not an Inproc one. Registering the dll would sort that out though.
Your dll will need to export DllRegisterServer and stuff. Create a new Automation Library and copy the relevant exports from the dpr.

Cheers,
Phil.
0
 
SmegboyAuthor Commented:
I added comobj to the uses clause in the mainapps unit and it did work ! Well Done.

The mjc.nwobject is used by everything !! this includes every module and the mainmenu itself. Should I simply create an object in the mainmenu and reference that object from all dlls?

.....


How? :)
0
 
philipleighsCommented:
Well yeah you could do that.

How? Add another parameter to the Start function (variant maybe?) and pass it across to each dll.

I can't say for sure if this'll work or not because I've never passed a com object across to a dll. There may be memory management issues.

Cheers,
Phil.
0
 
kambizCommented:
Hi Smegboy,

As an answer for your first problem that was disabling the focus on the main menu application I suggest this function that used by myself:

function RunApp(appName: PChar): Boolean;
var
   Buffer: array[0..255] of Char;
   Module: THandle;
begin
   Application.MainForm.Enabled := False;
   Module := ShellExecute(Handle, 'Open', appName, nil, nil, SW_SHOW);
   if Module > 0 then
      repeat
         Sleep(2000);
      until GetModuleFileName(Module, Buffer, SizeOf(Buffer)) = 0;
   Application.MainForm.Enabled := True;
   Result := Module > 0;
end;

I hope this helps you
0
 
SmegboyAuthor Commented:
Philip, I have tried compiling one of our apps into a dll and everything seems to be working really well except for one thing.

If an edit control on a form within a dll loses focus, you can't call setfocus to give it back again.

We do this a lot. I set up two buttons

the first did:
intedit1.enabled:=false;

the second went:
intedit1.enabled:=true;
intedit1.setfocus;

Any ideas how to fix this?

0
 
philipleighsCommented:
Hmm, that's strange. Prehaps create a skeleton exe and a skeleton dll and try to reproduce it in the simplest of environments.
That might show something up.

Maybe try
ActiveControl := intedit1;
or prehaps insert Application.ProcessMessages;

If you don't enable and disable, then does setfocus work?

Don't know about that one. I'm just guessing here.

Cheers,
Phil.

0
 
SmegboyAuthor Commented:
I have noticed if you perform the following code from within a dll it won't work.

edit1.enabled:=false;
edit1.enabled:=true;
edit1.setfocus;

this will only not work if edit1 ALREADY has focus when you implement this code.

our apps require response time where the app responds and you can't enter any more so this is very common.

this on the other hand does work.

{edit2 having focus}
edit2.enabled:=false;
edit1.enabled:=true;
edit1.setfocus;

and vice versa.

Whats the deal here?

Does the dll think that the edit control already has focus so doesn't send it the correct message, is this a bug or is there a work around?

Cheers,

Matt.
0
 
philipleighsCommented:
Instead of Edit1.SetFocus try

Windows.SetFocus(Edit1.Handle);

Does that make a difference?
0
 
SmegboyAuthor Commented:
You beauty, boy have you earned your 200 points.

Thanks for all your help
0
 
philipleighsCommented:
Yeah! It's been a good thread, I've enjoyed it.

I'll catch ya later.
Cheers,
Phil.
0

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

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