I am launching an application from my own application using
CreateProcess(...).
As I do not want my application to 'hang' while the other is running I
start a thread that has a nice dialog always on top to indicate to the
user that the other app is running.
But if the user click on my application it automatically brings it to
the front.
Because of my dialog box this is not really a problem, the user cannot
really do anything, but ideally I would like to keep the other
application on top as it has its own 'progress'.
I don't necessarily want it to be the top most window, I would just like
to keep it on top of my Window.
How would you achieve that?
Simon
When your window was activated (WM_MOUSEACTIVATE and WM_ACTIVATE), you can
call SetWindowPos without SWP_NOZORDER and set your window underneath those
of the other apps. This is a real pain, it requires you to iterate using
FindWindow and find the other app's window.
Maybe you can tweak your design a little. Add a Minimize button to your
dialog to make it easy to get your app out of the way or something.
-- David
Simon wrote:
--
HLS
The second dialog already sounds like it is a child (it stays in front of
his original window), but that doesn't prevent it from covering the windows
of the launched app.
-- David
The general best practice is that you set a boolean to some value (such as TRUE) while the
child process is running, and disable various menu items or controls that are
inappropriate whehn the child process is running, and re-enable them when the child
process terminates. See my essay on asynchronous process notification on my MVP Tips site
There is no way to manage Z-order for the desktop, since that is under control of the
user; your only way to override that is "always on top", but it is hard to get two apps to
cooperate in terms of managing Z-order.
I would be tempted to enable the child process to PostMessage to a parent app about its
progress, so in principle you could run it invisibly.
joe
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
Right.
He indicated he wasn't too concern about access to the main window
("he user cannot really do anything"), so why not just this process
'watch/progress" dialog modal with a auto close when finish?
Or is he talking about the child process window staying on top within
the parent window?
I guess, if its console, he can Allocate/Attach a Console which under
XP/2003+ I believe you can tie it within a region the parent window.
That use to be an old desire.
If its a GUI, that would be interesting.
--
HLS
I am not sure I follow.
>
> Or is he talking about the child process window staying on top within
> the parent window?
Not within the parent window, just on top of the app.
>
> I guess, if its console, he can Allocate/Attach a Console which under
> XP/2003+ I believe you can tie it within a region the parent window.
> That use to be an old desire.
>
> If its a GUI, that would be interesting.
>
Simon
The seconds dialog is a child, and it prevents the user from doing
anything at all, (the dialog is nothing more than a never ending
progress bar).
But when the user clicks anywhere on my app, (the app that called
CreateProcess( ... )), then it goes on top of all other windows
including the created process.
I would like the created process to go on top of my app.
Simon
>> Or is he talking about the child process window staying on top within
>> the parent window?
>
> Not within the parent window, just on top of the app.
Hi Simon,
So if you don't for within, the two windows can be side by side?
Anyway, you can adjust the code I have here.
Maybe there is an more cleaner way to do this, but I played with it
and came up with the following:
This approach uses the parent window solely to control the placement
of the child process window which in this example is a GUI applet. In
other words, you don't have to change the child process. No matter
what is spawn, this logic will keep it on top of the parent applet.
1) Add the following to the parent main window class, in this case, I
have a parent EXE with a MFC dialog, CMyParentDlg.
class CMyParentDlg : public CDialog
{
....
public:
BOOL RunChildProcess(const char * szfn, const char * szopts);
static BOOL CALLBACK EnumWindowProc(HWND hwnd, LPARAM lParam);
afx_msg void OnMove(int x, int y);
public:
PROCESS_INFORMATION cpi;
HWND m_hWndChild;
}
2) Add the methods:
BOOL CALLBACK CMyParentDlg::EnumWindowProc(HWND hwnd, LPARAM lParam)
{
//
// find the child process thread id window handle
//
CMyParentDlg *pDlg = (CMyParentDlg *)lParam;
DWORD wndPid = 0;
DWORD wndTid = GetWindowThreadProcessId(hwnd, &wndPid);
if (wndTid == pDlg->cpi.dwThreadId) {
pDlg->m_hWndChild = hwnd;
}
return TRUE;
}
BOOL CMyParentDlg::RunChildProcess(const char * szfn, const char * szopts)
{
STARTUPINFO si = {0};
ZeroMemory(&cpi,sizeof(cpi));
//
// initialize the startup window position and size for the
// child process. Make it slightly smaller.
//
WINDOWPLACEMENT wp = {0};
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(&wp);
si.dwFlags |= STARTF_USEPOSITION | STARTF_USESIZE;
si.dwX = wp.rcNormalPosition.left;
si.dwY = wp.rcNormalPosition.top;
si.dwXSize = wp.rcNormalPosition.right-wp.rcNormalPosition.left-50;
si.dwYSize = wp.rcNormalPosition.bottom-wp.rcNormalPosition.top-50;
//
// Start the process
CString sCmd(szfn);
if (szopts && szopts[0]) {
sCmd += " ";
sCmd += szopts;
}
if (!CreateProcess(szfn,(LPSTR)szopts,
NULL,NULL,FALSE,0,NULL,NULL,&si,&cpi)) {
return FALSE;
}
//
// Get the window handle for the child process
// See Note 1 about the EnumWindow Loop
//
DWORD waitTics = GetTickCount() + 5*1000; // wait 5 seconds
while (GetTickCount() < waitTics) {
EnumWindows((WNDENUMPROC)EnumWindowProc, (LPARAM) this);
if (m_hWndChild) break;
Sleep(250);
}
//
// make it top most. See note #3
//
if (m_hWndChild) {
::SetWindowPos(m_hWndChild, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
CloseHandle(cpi.hThread);
TRACE("- WAIT: child process\n");
while (WaitForSingleObject(cpi.hProcess, 1) == WAIT_TIMEOUT)
{
MSG msg;
if (PeekMessage(&msg,this->m_hWnd,0,0,PM_REMOVE)){
::TranslateMessage(&msg);
::DispatchMessage(&msg);
//
// See Note 2 about these breaks
//
if (msg.message == WM_QUIT) break;
if (msg.message == WM_CLOSE) break;
if (msg.message == WM_SYSCOMMAND) {
if (msg.wParam == SC_CLOSE) {
break;
}
}
}
}
CloseHandle(cpi.hProcess);
TRACE("- DONE: child process\n");
m_hWndChild = NULL;
return TRUE;
}
void CMyParentDlg::OnMove(int x, int y)
{
//
// Move child process window to follow the parent window
//
CDialog::OnMove(x, y);
if (m_hWndChild) {
WINDOWPLACEMENT wp = {0};
wp.length = sizeof(WINDOWPLACEMENT);
if (::GetWindowPlacement(m_hWndChild,&wp)) {
UINT cx = wp.rcNormalPosition.right-wp.rcNormalPosition.left;
UINT cy = wp.rcNormalPosition.bottom-wp.rcNormalPosition.top;
::MoveWindow(m_hWndChild,x,y,cx,cy,true);
}
}
}
Using the above, you get what you want. You have to make the
adjustments with how the child window is initially displayed and move
as the parent window is move. You can also add a OnSize() to make
adjustments there as well.
There are two things to probably work on:
Note #1: EnumWindow Loop
When the child is started, the m_hWndChild may not exist. This allow
allows you to wait until there is a child window. Other ways:
1) Wait until the child is waiting for input before calling
EnumWindow. This might not always be reliable.
2) If you have source control of the child, have it create a Named
Mutex that the parent can easily detect (by trying to open it). This
is the preferred way, if you have child source control.
Note #2: TerminateProcess()
When the chile process is called and the parent is waiting for
completion, it needs to a message loop to keep the parent message pump
running. Depending on how much control the user has on the parent
process, it may close before the child process is complete.
So you probably have logic to determine if it needs to be terminated
with the various break messages, like WM_QUIT.
Note #3: Topmost
The topmost along with the OnMove() makes it more like its on top of
the parent applet. But its really a desktop top most. There is
probably some placement logic that you can do without making it a
desktop topmost.
Anyway, the above is a start. It shows how to control the child
process window to make it follow the parent placement rules
independent of making changes to the child process.
I appreciate other people's comment as I learned as most doing it, and
I won't be surprise if there is better logic. :)
--
HLS
This could be made to work, but I recommend simply: don't show the child
window with the progress bar; just MINIMIZE the app until the launched one
finishes. Then RESTORE the app. If the first app is clicked on in the
tasbar, show a message box "Waiting for the second app to finish. Click OK"
and that's it.
Why waste your life managing the first app when it's not doing anything
useful?
-- David
I agree 100% the thought has to be considered for his need, but give
him the benefit of doubt he has a reason as I personally come across
designs, not necessarily where child(s) have to be on top, but the
some user control of the parent was still necessary, i.e. to abort the
process, show a hidden window. He has another "progress" dialog for
that (I presume) himself.
Examples:
1) In a GUI configuration tool, it displays a "Control Panel" like
list view. Some icons start internal functions (dialogs), others
launch external EXEs. Some of the EXEs can not at the same time, some
works as if it was modal (the parent can't continue starting other
stuff).
But the parent watches these EXEs until complete. For the non-modal
types, the user can start other exes or internal dialogs. If the
parent is closed (exited), it terminates any child EXE.
2) An online hosting GUI monitor, operators can see users that are
online. He can disconnect them, send them a message, but he can click
the line item to launch a context (spying) monitor. The online user
can start other exes (start a game perhaps by command). In most
cases, the hosting monitor needs to stop any that are a) locked or
when it exits. In some cases, the child process can continue.
Of course, none of these are controlling the window position, except
when the user starts the exe: in early versions started in minimized
mode, and current versions in hidden mode because operators didn't
want all these windows open. When hidden, it can be activated via
"VIEW | process XYZ" menu command, added/removed dynamically.
These are just a few designs cases where operator GUI control of the
parent is required. Also, they can be in the system tray.
He can maybe disable the QUIT/CLOSE of the parent. Make it opaque
perhaps? Put in the system tray, etc.
--
HLS
Simon, I always get confused and need a refresher when it comes to
screen, relative screen positions and what functions to use. Here are
clean up for better placement and control of the child window.
Add a member to the class and WM_TIMER handler to the class:
WINDOWPLACEMENT wppLast;
and the changes to the functions:
BOOL CMyParentDlg::RunChildProcess(const char * szfn, const char * szopts)
{
STARTUPINFO si = {0};
ZeroMemory(&cpi,sizeof(cpi));
//
// initialize the startup window position and size for the
// child process
//
si.dwFlags |= STARTF_USEPOSITION | STARTF_USESIZE;
RECT rp;
GetClientRect(&rp);
ClientToScreen(&rp);
si.dwX = rp.left;
si.dwY = rp.top;
si.dwXSize = rp.right - rp.left-50;
si.dwYSize = rp.bottom - rp.top-50;
//
// Start the process
if (!CreateProcess(szfn,(LPSTR)szopts,NULL,
NULL,FALSE,0,NULL,NULL,&si,&cpi)) {
return FALSE;
}
//
// Get the window handle for the child process
// See Note about the Sleep
//
DWORD waitTics = GetTickCount() + 5*1000; // wait 5 seconds
while (GetTickCount() < waitTics) {
EnumWindows((WNDENUMPROC)EnumWindowProc, (LPARAM) this);
if (m_hWndChild) break;
Sleep(250);
}
if (m_hWndChild) {
::SetWindowPos(m_hWndChild, HWND_TOPMOST,
0, 0, 0, 0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
CloseHandle(cpi.hThread);
SetTimer(2,5,NULL);
return TRUE;
}
void CMyParentDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 2 && cpi.hProcess) {
if (WaitForSingleObject(cpi.hProcess, 0) != WAIT_TIMEOUT) {
KillTimer(nIDEvent);
CloseHandle(cpi.hProcess);
cpi.hProcess = 0;
m_hWndChild = NULL;
}
}
CDialog::OnTimer(nIDEvent);
}
void CMyParentDlg::OnMove(int x, int y)
{
//
// Move child process window to follow the parent window
//
WINDOWPLACEMENT wpp = {0};
wpp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(&wpp);
CDialog::OnMove(x, y);
if (m_hWndChild) {
int xdiff = wppLast.rcNormalPosition.left -
wpp.rcNormalPosition.left;
int ydiff = wppLast.rcNormalPosition.top -
wpp.rcNormalPosition.top;
TRACE("- 1 x: %6d xdiff: %6d | y: %6d ydiff: %6d\n",x,
xdiff,y,ydiff);
WINDOWPLACEMENT wpc = {0};
wpc.length = sizeof(WINDOWPLACEMENT);
if (::GetWindowPlacement(m_hWndChild,&wpc)) {
x = wpc.rcNormalPosition.left - xdiff;
y = wpc.rcNormalPosition.top - ydiff;
UINT cx = wpc.rcNormalPosition.right-wpc.rcNormalPosition.left;
UINT cy = wpc.rcNormalPosition.bottom-wpc.rcNormalPosition.top;
::MoveWindow(m_hWndChild,x,y,cx,cy,true);
}
}
wppLast = wpp;
}
I used a timer to watch for the process to end. Either way work, this
was cleaner because if you don't break out of the internal message
loop in the previous code, you can get a GPF. Also it shows you don't
need a separate thread to watch for the process to end.
And the OnMove() better moves the child window relative to the parent,
and like David said, doesn't overlap the title bar.
--
HLS
David, I agree. But there are legit reasons why a user still need some
parent process control. All depends on the needs.
--
HLS
David Ching wrote:
--
HLS
But I'd be inclined to use the solution I originally proposed: when the app is launched,
use CreateProcess with an option to minimize the second app, and put it in a mode where it
merely notifies the parent of the progresss.
joe
>
> So if you don't for within, the two windows can be side by side? Anyway,
> you can adjust the code I have here.
The main app, (the one creating the process), can be maximized.
>
> I appreciate other people's comment as I learned as most doing it, and I
> won't be surprise if there is better logic. :)
>
Thanks, I'll give it a try on my side and get back to you
Simon
Yes, that could work. I'll play around with it.
>
> But I'd be inclined to use the solution I originally proposed: when the app is launched,
> use CreateProcess with an option to minimize the second app, and put it in a mode where it
> merely notifies the parent of the progresss.
>
Sorry I did not see any any original reply on my server.
But realistically that might cause more problems, there is nothing
stopping the user from maximizing the second app.
I would prefer, (in an ideal situation), the first app to bring the
second app on top.
Simon
But you seem to want something that solves a problem not worth solving, which is to take
control away from the user about Z-order and application size. If you want to take that
control away, take it away completely by eliminating the need to have it at all, such as
by running the app minimized or invisible and letting it notify its parent about its
progress; if the parent wishes to display that progress, that's cool, but you need to
provide the interface. Use a Registered Window Message (see my essay on Windows Message
Management on my MVP Tips site for details)
joe