I created child window (dialog) end set it's parent the window of another process (Notepad for example) by its handle.
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, processID );
if (NULL != hProcess )
{
HWND hw;
hw = find_main_window(processID); //some function of getting win handle through process ID
}
................
CMyHud *mhDlg = new CMyHud();
CWnd* pWnd = CWnd::FromHandle(hw);
//if(mhDlg->m_hWnd != 0)
if (!mhDlg->GetSafeHwnd())
{
if (mhDlg != NULL)
{
ret = mhDlg->Create(IDD_DIALOG1, pWnd);
}
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
return FALSE;
}
}
Then I set styles for parent and child windows
LONG t = GetWindowLong(hw,GWL_STYLE) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
SetWindowLong(hw,GWL_STYLE,t);
LONG t1 = GetWindowLong(mhDlg->m_hWnd,GWL_STYLE) | WS_CLIPSIBLINGS | WS_OVERLAPPED;
SetWindowLong(mhDlg->m_hWnd,GWL_STYLE,t1);
::BringWindowToTop(mhDlg->m_hWnd);
mhDlg->ShowWindow(SW_SHOW);
The child window appears in the client area of the parent window (Notepad).
Good.
BUT! It's disappears when I set focus on parent window. Well. Physically it's still there, but its background merges with parent's window background, and it seems like the child window is gone.
When you find child window and set focus on it, it's appearing again. But it is redrawing bad, still having a parts of parent's window background (look at the picture).
What have i done wrong?? What should I do for appearing child window over the parent ALWAYS, regardless of redrawing the parent window?
With using the SetWindowPos method all works perfectly!
Related
I have a 32-bit MFC application that uses a custom library that would be a nightmare to re-compile into x64. In general the app doesn't really need to run as 64-bit, except in one case -- and that is to render contents to display in a dialog window, which can benefit from a larger addressing space.
So my goal is to "imitate" CDialog::DoModal method but for a dialog in another process.
I built that dialog window as a standalone x64 MFC dialog-based application. It takes a file path as it's input parameter, does all the work internally, and returns simple user selection: OK, Cancel.
So I do the following from my main parent process:
//Error checks omitted for brevity
CString strCmd = L"D:\\C++\\MyDialogBasedApp.exe";
HWND hParWnd = this->GetSafeHwnd();
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
sei.nShow = SW_SHOW;
sei.lpVerb = _T("open");
sei.lpFile = strCmd.GetBuffer();
sei.hwnd = hParWnd;
BOOL bInitted = SUCCEEDED(::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
ShellExecuteEx(&sei);
DWORD dwProcID = ::GetProcessId(sei.hProcess);
//Try to get main Wnd handle for the child process
HWND hMainChildWnd = NULL;
for(;; ::Sleep(100))
{
hMainChildWnd = getHwndFromProcID(dwProcID);
if(hMainChildWnd)
break;
}
HWND hPrevParWnd = ::SetParent(hMainChildWnd, hParWnd);
if(hPrevParWnd)
{
//Wait for child process to close
::WaitForSingleObject(sei.hProcess, INFINITE);
//Reset parent back
::SetParent(hMainChildWnd, hPrevParWnd);
}
::CloseHandle(sei.hProcess);
if(bInitted)
::CoUninitialize();
where getHwndFromProcID is taken from here.
This kinda works, except of the following:
(1) There are two icons on the taskbar: one for my main app, and one for the child app. Is there a way not to show a child icon?
(2) I can switch focus from child window to parent and vice versa. In actual modal dialog window one cannot switch back to parent while child is open. Is there a way to do that?
(3) If I start interacting with the parent, it appears to be "hung up" and the OS will even show it on its title bar.
So I was curious if there's a way to resolve all these?
you need pass pointer of own window to child process
you need process windows messages, while you wait on child process
exit. WaitForSingleObject unacceptably here - need use
MsgWaitForMultipleObjectsEx
child process must set your window as self owner window at create
time - you not need call SetParent
with this all will be worked perfect. in your 32-bit MFC application you need use next code :
BOOL DoExternalModal(HWND hwnd, PCWSTR ApplicationName)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
WCHAR CommandLine[32];
swprintf(CommandLine, L"*%p", hwnd);
if (CreateProcessW(ApplicationName, CommandLine, 0, 0, 0, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
MSG msg;
for (;;)
{
switch (MsgWaitForMultipleObjectsEx(1, &pi.hProcess, INFINITE, QS_ALLINPUT, 0))
{
case WAIT_OBJECT_0:
CloseHandle(pi.hProcess);
return TRUE;
case WAIT_OBJECT_0 + 1:
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
continue;
default: __debugbreak();
}
}
}
return FALSE;
}
in MyDialogBasedApp.exe let use MessageBox as demo dialog. we will be use your MFC window as first argument to it.
void ExeEntry()
{
int ec = -1;
if (PWSTR CommandLine = GetCommandLine())
{
if (CommandLine = wcschr(CommandLine, '*'))
{
HWND hwnd = (HWND)(ULONG_PTR)_wcstoi64(CommandLine + 1, &CommandLine, 16);
if (hwnd && !*CommandLine && IsWindow(hwnd))
{
ec = MessageBoxW(hwnd, L"aaa", L"bbb", MB_OK);
}
}
}
ExitProcess(ec);
}
with this code:
(1) There are only one icon on the taskbar for your main app
(2) You can not switch focus from child window to parent and vice versa. all work as actual modal dialog window
(3) parent not "hung up" because it processing windows messages (MsgWaitForMultipleObjectsEx) - your code is "hung up" because you not do this, but wait in WaitForSingleObject
A modal dialog box does two things that make it "modal":
The dialog's "owner" is set to the parent window.
The parent window is disabled.
I played around with this a little bit and while you can do these manually, the easiest way is to just pass the parent window handle to the DialogBox function (or the CDialog constructor in MFC).
Instead of doing all the work after ShellExecuteEx in the parent process, your child process can use FindWindow (or a similar mechanism) to get the parent window handle and use that to display the dialog.
What you are trying to do cannot safely be done. The blog entry Is it legal to have a cross-process parent/child or owner/owned window relationship? explains, that installing a cross-process owner/owned window relationship causes the system to call AttachThreadInput, and - as we all know - AttachThreadInput is like taking two threads and pooling their money into a joint bank account, where both parties need to be present in order to withdraw any money. This creates a very real potential for a dead lock. You can only safely prevent this from happening, if you control both participating threads. Since at least one thread uses a 3rd party application framework (MFC in this case), this is off limits.
Since we have established, that your proposed solution cannot safely be implemented, you need to look into alternatives. One solution might be to delegate the work to a 64-bit process for computation, and have the results passed back to your 32-bit process for display.
I am using Windows MFC to create a small program.
I would like to make multiple instances of the program appear in a cascaded position(s).
Currently the program always appear centered, i.e. it is not possible to see it multiple windows.
Is there an automatic way to let windows create multiple instance in cascaded positions?
To test i use a batch script with multiple lines of:
"start MyProgram.exe"
"start MyProgram.exe"
"start MyProgram.exe"
The dialogs i use are derived from CDialogEx (but i had same using CDialog)
I expected this to be a flag/properties of the dialog.
Before changing the .rc-file have properties like this
IDD_MAIN_DLG DIALOGEX 0, 0, 260, 185 STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION
I am aware of the CascadeWindows() function, but to my knowledge it requires more awareness of which instances that already run
How about the following code as a starting point?
#include <Psapi.h>
namespace {
size_t nWnds = 0;
HWND hWnds[10];
BOOL CALLBACK enumerate(HWND hWnd, LPARAM This)
{
HWND hWndThis = reinterpret_cast<HWND>(This);
TCHAR nameThis[MAX_PATH], nameOther[MAX_PATH];
VERIFY(GetWindowModuleFileName(hWndThis, nameThis, _countof(nameThis)));
TCHAR wndclass[32];
VERIFY(RealGetWindowClass(hWnd, wndclass, _countof(wndclass)));
if (_tcscmp(wndclass, _T("#32770")) == 0) {
DWORD pid;
GetWindowThreadProcessId(hWnd, &pid);
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (hProcess != NULL) {
if (GetModuleFileNameEx(hProcess, NULL, nameOther, _countof(nameOther))) {
if (_tcscmp(nameThis, nameOther) == 0) {
hWnds[nWnds++] = hWnd;
}
}
VERIFY(CloseHandle(hProcess));
hProcess = NULL;
}
}
return TRUE;
}
}
BOOL CMFCApplication1Dlg::OnInitDialog()
{
// ...
VERIFY(EnumWindows(enumerate, reinterpret_cast<LPARAM>(m_hWnd)));
if (nWnds > 1) {
VERIFY(CascadeWindows(NULL, MDITILE_ZORDER, NULL, nWnds, hWnds));
}
return TRUE;
}
It consists of a change to OnInitDialog to scan for all top level dialogs that have been created by your executable and then call CascadeWindows. Of course in enumerate you can also move every window you find to a point that starts at CPoint(x, y) and changes by CSize(xoffset, yoffset) with every found window.
Some things to keep in mind:
CascadeWindows does not look like the right solution, it restores all maximized windows and does not only touch windows created by your process (which I would prefer).
If your process creates multiple top level dialogs, then you might need to detect which dialogs to move.
If the user makes a copy of your program file then the module file name will be different.
Just proof of concept code, you'll need to add error checking and bounds checking.
CreateWindowEx API really posts WM_SIZE message?
When I create a window via CreateWindowEx as full screen mode,
CreateWindowEx posts WM_SIZE but window mode doesn't.
My code sets the window style like this :
if(bFullscr)
{
//When the window is in full screen mode.
nStyle = WS_POPUP;
nExtraStyle = WS_EX_APPWINDOW;
}
else
{
//Otherwise.
nStyle = WS_OVERLAPPEDWINDOW;
nExtraStyle = (WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
}
And changes display settings like this (full screen mode only) :
if(bFullscr)
{
DEVMODE sScrSet;
memset(&sScrSet, 0, sizeof(DEVMODE));
sScrSet.dmSize = sizeof(DEVMODE);
sScrSet.dmPelsWidth = nWidth;
sScrSet.dmPelsHeight = nHeight;
sScrSet.dmBitsPerPel = nColorBit;
sScrSet.dmFields = (DM_BITSPERPEL | DM_PELSHEIGHT | DM_PELSWIDTH);
if(ChangeDisplaySettings(&sScrSet, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
//Error routine.
}
}
I'm really wonder why CreateWindowEx posts WM_SIZE message selectively.
If you simply want to resize the window, somewhere in your code you should have ShowWindow(hWnd, nCmdShow); change it as follows:
ShowWindow(hWnd, SW_SHOWDEFAULT);//show normal
ShowWindow(hWnd, SW_SHOWMAXIMIZED);//show maximized (full screen)
SetWindowPos(hWnd, NULL, 10, 10, 300, 300, SWP_SHOWWINDOW);//show at specific position
Also you could use WS_MAXIMIZE in CreateWindow, but that could complicate things. Window usually has WS_OVERLAPPEDWINDOW or WS_POPUP|WS_CAPTION|WS_SYSMENU. You should pick one and keep it simple.
When Window size changes, it receives WM_SIZE, you can catch that and examine it.
I'm currently using SHBrowseForFolder() to open a browse folder window but how do I return focus to my main window when Cancel / OK is pressed. I read that I should re-enable my main window before the dialog closes but where is that exactly? Any thoughts?
void buttonPush(HWND hWnd) {
EnableWindow(hWnd, FALSE);
BROWSEINFO bi = { 0 };
TCHAR szDir[MAX_PATH] = { 0 };
LPITEMIDLIST pid = NULL;
LPMALLOC pMalloc = NULL;
if (SUCCEEDED(SHGetMalloc(&pMalloc)))
{
ZeroMemory(&bi,sizeof(bi));
bi.hwndOwner = NULL;
bi.pszDisplayName = NULL;
bi.pidlRoot = NULL;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT | BIF_USENEWUI;
bi.lpfn = BrowseCallbackProc;
pidl = SHBrowseForFolder(&bi);
if (pidl)
{
// Folder selected in dialog
pMalloc->Free(pidl);
}
pMalloc->Release();
}
EnableWindow(hWnd, TRUE);
}
Instead of enabling and disabling your main window, just set bi.hwndOwner = hWnd; Then it will enable and disable automatically.
EnableWindow(hWnd, false);
This goes wrong because you are helping too much. When the dialog closes, there is no window left in your application that can still receive the focus. Your hWnd is still disabled, it doesn't get enabled until later. So the Windows window manager is forced to find another window to give the focus to. That will be the window of another app. Inevitably your window will disappear behind it.
Delete the EnableWindow() calls. That is enough, but you can tell the dialog about your window so it won't have to guess at it, useful if your window isn't the active window for some reason:
bi.hwndOwner = hWnd;
Is there a way to loop through all MFC Child Dialogs, MDI frames and etc? And is there a way to find out which dialog or window I am looping through?
Taken from Анатолий Тутов (https://web.archive.org/web/20140110220804/http://www.asis.ru/posts/27):
for (CWnd *pWnd = GetWindow(GW_CHILD); pWnd != NULL; pWnd = pWnd->GetNextWindow(GW_HWNDNEXT))
{
//Insert your code here. pWnd is a pointer to control window.
}
You could use EnumChildWindows to iterate through child windows of certain window.