Re: CreateProcess( ... ) always on top of my app.
Hector Santos wrote:
Simon wrote:
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. :)
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