Link to home
Start Free TrialLog in
Avatar of akalmani
akalmani

asked on

Modal Process

I have 2 applications. Say App1 and App2. Both are MFC dialog based applications. App1 invokes App2 using ShowModal(). Now I want App1 to wait until App2 is finished. This can be accomplished by MsgWaitForMultipleObjects(). Here I if I recieve WM_PAINT message for App1 then I allow it to recieve to App1. Any other i/p has to be removed and App2 has to have focus.

My requirement is if the user tries to do anything(mouse click, ALT+TAB, keydown) with App1 when App2 is open. I always want App2 to have focus.
I tried catching all the messages (but it flickers and its not the right solution). I tried catching specific messages but no luck. Anyidea how to achieve this?

DWORD dwID = 0;
static WNDPROC pWndProc = NULL;

static LRESULT CALLBACK InvokeWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
      case WM_ACTIVATE:case WM_ACTIVATEAPP: case WM_LBUTTONUP: case WM_RBUTTONUP:
      case WM_NOTIFY:
            EnumThreadWindows(dwID, ModalEnumProc, 0);        
        break;
    }

    return CallWindowProc(pWndProc, hwnd, msg, wParam, lParam);
}

//Pass the full path of the .exe file. e.g "C:\App2.exe"
BOOL ShowModal(IN CString PcstrPath)
{
      BOOL fRetVal = FALSE;
      HANDLE hWait[2] = {NULL, NULL};

      do
      {
            if(PcstrPath.IsEmpty())//Cannot be empty
                  break;

            //Get the startup info
            STARTUPINFO sStartupInfo;
            ZeroMemory((void*)&sStartupInfo, sizeof(STARTUPINFO));
            sStartupInfo.cb = sizeof(STARTUPINFO);
            GetStartupInfo(&sStartupInfo);

            PROCESS_INFORMATION sProcessInfo;
            ZeroMemory((void*)&sProcessInfo, sizeof(PROCESS_INFORMATION));

            //Create the process
            if(!(fRetVal = CreateProcess(NULL, PcstrPath.GetBuffer(PcstrPath.GetLength()),
                  NULL, NULL, FALSE, 0, NULL, NULL, &sStartupInfo, &sProcessInfo)))
            {
                  break;
            }

            pWndProc = (WNDPROC)::SetWindowLong(AfxGetMainWnd()->m_hWnd,
                             GWL_WNDPROC, (LONG)&InvokeWindowProc);

            DWORD dwResult = 0;
            MSG msg;

            //Create the event so that we can break from the while loop in case we
            //recieve WM_QUIT, WM_QUERYENDSESSION or WM_CLOSE
            hWait[0] = CreateEvent(NULL, FALSE, FALSE, NULL);

            //Wait for the process to finish
            hWait[1] = sProcessInfo.hProcess;
            dwID = sProcessInfo.dwThreadId;

            //The message loop lasts until the other exe closes
            while(1)
            {
                  //Read all of the messages in this loop, first since MsgWaitForMultipleObjects
                  //will check the time and wait hence these messages might not be removed.
                  //Remove each message as we read it.
                  while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                  {
                        //Quit message, exit. Cannot be
                        if(WM_QUIT == msg.message || WM_CLOSE == msg.message ||
                              WM_QUERYENDSESSION == msg.message)  
                        {
                              SetEvent(hWait[0]);
                              break;
                        }
                        
                        
                        //Handle these messages to set focus to spawned application
                        switch(msg.message)
                        {
                        case WM_PAINT://Dispatch only WM_PAINT message
                              DispatchMessage(&msg); //Let it paint the window
                              break;
                        default:
                              break;
                        }
                  }//End of while message loop

                  //Wait for all messages sent or posted to this queue
                  //or for one of the passed handles be set to signaled.
                  dwResult = MsgWaitForMultipleObjects(2, &hWait[0],
                                                FALSE, INFINITE, QS_ALLINPUT);

                  if((WAIT_OBJECT_0 + 0) == dwResult || (WAIT_OBJECT_0 + 1) == dwResult)
                  {
                        //Our process was killed or Event was set
                        break;
                  }
                  else
                  {
                        //New messages have arrived.
                        continue;
                  }
            } //End of message while loop.
      }while(0);//End of Main while loop

      if(hWait[0])//Close our event
      {
            CloseHandle(hWait[0]);
            hWait[0] = NULL;
      }

      (void)::SetWindowLong(AfxGetMainWnd()->m_hWnd, GWL_WNDPROC, (LONG)prev_windowproc);

      return fRetVal;
}


BOOL CALLBACK ModalEnumProc(IN HWND PhWnd, IN LPARAM lParam)
{
      if(PhWnd)
      {
            //Get the foreground window
            //DWORD dwThreadID = ::GetWindowThreadProcessId(::GetForegroundWindow(), NULL);

            //DWORD dwCurThreadID = ::GetCurrentThreadId();

            //Connect to the actual foreground window thread
            //::AttachThreadInput(dwCurThreadID, dwThreadID, TRUE);

            //Set focus to this our spawned application
            ::SetForegroundWindow(PhWnd);
            ::SetFocus(PhWnd);

            //Disconnect the actual foreground windows thread
            //::AttachThreadInput(dwCurThreadID, dwThreadID, FALSE);
      }

    return TRUE;
}

Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

Hide App1 until App2 finishes?
plz have a look at the following link

the text is in chinese but the code is legible :)

http://www.vchelp.net/vchelp/zsrc/dists.asp?type_id=25&class_id=1&cata_id=2&article_id=458&search_term=
Avatar of akalmani
akalmani

ASKER

AndyAinscow:
No I want the App1 to stay since the User might be surprised. I want to simulate a modal behavior.

amrit_82:
I do not want to disable ALT+TAB since this is not a good solution for a product. I managed to catch ALT+TAB please refer my code.

Only problem I am facing is when the user clicks on App1(anywhere on the dialog, button, control...), the focus is not set to App2. What messages do I need to catch in InvokeWindowProc() there lies the answer. Also I am sure that ModalEnumProc() which I have written does not setfocus to App2 correctly.
When you call App2 use
AfxGetMainWnd()->EnableWindow(false);
to disable the App1 (and conversely with true after App2 is closed to re-enable it).

Is that any use?
The DoModal of a dialog is similar - that disables the parent frame of the window owning the dialog.
AndyAinscow:
Yes I did try it before posting this code but no luck. I had the same impression before. But requirement is to setfocus to App2 whenever user tries to mess-up with App1. Also I will not know when to set the focus to App2 since I will not recieve any messages when I disable the window of App1.

Also the DoModal exist in the same process. But here we have a completely different scenario.
This might not work.
Disable App1.
Look for mouse messages in the mainframe of app1.
When you get a mouseclick you then use SetForeGroundWindow (SetFocus, post WM_ACTIVATEAPP) to activate App2.

Have you got a window handle for App2 after it is launched from App1?
Reading your question makes me think i would use CreateProcess and then WaitForSingleObject....

If you create a regular dialog and domodal and you click the parent window it also flickers... so isn't that the behavior you are trying to emulate anyway?
AndyAinscow:
----------------
>>>>When you get a mouseclick you then use SetForeGroundWindow (SetFocus, post WM_ACTIVATEAPP) to activate App2.
Which mouse message do I track? There are many messages that are posted? Can you be specific. This is what I am looking for?

>>>>Have you got a window handle for App2 after it is launched from App1?
Not a problem I can store it, that means I have App2 window handle.

Popoi:
-------
>>>>>If you create a regular dialog and domodal and you click the parent window it also flickers... so isn't that the behavior you are trying to emulate anyway?

I have already tried this, only problem here is, you get "Not responding" and since its a blocking call, it does not WM_PAINT messages. In my opinion this is not the right solution.
Try WM_LBUTTONDOWN.

I haven't tried to do what you want so this may not work.  (I can't see a problem with the logic though so it ought to work).
AndyAinscow:
----------------
On Weekends I modified my InvokeWindowProc() and handled the corresponding messages. Now it works except in 1 situation.

I think surely we can simulate this behavior. When I move any other window/application(that has focus obivously), the title bar of App1 shows that it has focus (Blue color, hope u understand what i mean). This should not be the case since the other window/application has focus.
I know the solution lies in WM_PAINT, but I cannot setfocus to App2 since this might flicker the window, as too many WM_PAINT messages arrive at this time. Any hints for me, how it can be achieved. I think this will be a great if it works.

//Updated Code begins here. Rest all functions are same, no changes
HWND g_hWnd = NULL;        //This variable holds the handle of App2 in ModalEnumProc.

static LRESULT CALLBACK InvokeWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
     case WM_ACTIVATE:case WM_ACTIVATEAPP: case WM_MOUSEACTIVATE: case WM_KEYDOWN:
     case WM_NCLBUTTONDOWN: case WM_LBUTTONDOWN: case WM_NCLBUTTONUP: case WM_RBUTTONDOWN:
     case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP: case WM_LBUTTONDBLCLICK: case WM_NCLBUTTONDBLCLICK:
     case WM_RBUTTONDBLCLICK: case WM_NCRBUTTONDBLCLICK: case WM_NCACTIVATE: case WM_NCPAINT:
     case WM_SYSKEYDOWN: case WM_PRINTCLIENT: case WM_CTLCOLORDLG: case WM_ERASEBKGND:
     case WM_SYNCPAINT:
          if(NULL == g_hWnd)
                EnumThreadWindows(dwID, ModalEnumProc, 0);        
         else
         {
                 SetFocus(g_hWnd);
                 SetForegroundWindow(g_hWnd);
         }
        break;
    }

    return CallWindowProc(pWndProc, hwnd, msg, wParam, lParam);
}
You could try to turn window updating off for App2 temporarily.
LockWindowUpdate() and UnlockWindowUpdate()
This prevents window repainting and should reduce the WM_PAINT messages being fired.
I tried LockWindowUpdate it does not work either.
ASKER CERTIFIED SOLUTION
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi AndyAinscow I got the code working. Thanks for your help. Your moral support helped me to achieve the goal.
Thanks and glad to hear it helped.