Creating multiple MFC dialogs through COM, strange behaviour - c++

Updated: please see this other thread instead, all this COM stuff is not part of the problem.
One of our apps has a COM interface which will launch a dialog, e.g:
STDMETHODIMP CSomeClass::LaunchDialog(BSTR TextToDisplay)
{
CDialog *pDlg = new CSomeDialog(TextToDisplay);
pDlg->BringWindowToTop();
}
For some reason when the COM method is called several times at once by the server, we get odd behaviour:
We get multiple dialogs, but only one entry in the taskbar
Dialog Z-order is based on order created and can't be changed... the first dialog created is always shown under the 2nd one, 2nd under 3rd, etc, even when you drag them around
if N dialogs were created, closing one of them closes it and all the others created afterwards. e.g if 5 dialogsa re created and you close the 3rd one, #3,#4,#5 all get closed.
It's somehow like the dialogs are siblings but I don't see anything weird going on. Is it perhaps due to COM, or is this a weird MFC/Win32 issue?
EDIT: If the interface method is called several times separately, it works as expected. Only when the server component sends several through at once does it seem to mess up. Could threading/timings be to blame?
EDIT2:
I put this logging in:
std::stringstream ss;
HWND self = dlg->m_hWnd;
HWND parent = dlg->GetParent() ? dlg->GetParent()->m_hWnd : 0;
ss<<"Dlg created'. HWND = "<<self<<", Parent = "<<parent<<std::endl;
OutputDebugString(ss.str().c_str());
It gave:
Dlg created. HWND = 0013014A, Parent = 00000000
Dlg created. HWND = 001B0390, Parent = 0013014A
Dlg created. HWND = 000B03B0, Parent = 001B0390
So clearly the problem is the dialogs are being made children of each other. But the question is, WHY?! It seems Windows is doing this automatically...
This question seems to be slightly away from the main issue of parenting, so I have tried to separate out the main issue into a new question.

It sounds like the first dialog has been set as the owner of the second, and the second as the owner of the third. Can you change the dialog initialization to explicitly specify the owner window? Is there a window that makes sense to assign? Perhaps the Desktop window, if they're all intended to be top-level?
If you want to be able to access all three (or more), then they would need to be modeless. Try using Create(CSomeClass::IDD, CWnd::GetDesktopWindow()), and you ought to see sibling dialogs, all of which show up on the taskbar.

Related

Make focused modeless dialog topmost

So I have my main dialog that calls modeless dialogs through this function (this is the legacy code on the project):
void MyClass::ShowDialog(CDialog* dialog)
{
if (!IsWindow(dialog->m_hWnd))
{
return;
}
int nCmdshow1 = dialog->IsWindowVisible() ? SW_HIDE : SW_SHOW;
dialog->ShowWindow( nCmdshow1 );
}
Problem: all sub dialogs stay on top of my main dialog.
Desired behavior: whichever's focused (they are all modeless), be it the main dialog, or sub dialogs, I want it to be the topmost dialog. Thank you!
Note: I already tried on my main dialog's OnInitDialog() these but didn't work:
1. SetWindowPos(&this->wndTop,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
2.SetWindowPos(&this->wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
EDIT
Also, sub dialogs are created this way:
m_subDlg1->Create( SubDlg1::IDD, this );
As long as there is an owner relation between two windows. the owner of a window can never be on top of the owned window.
Windows in an owner, parent, child relation always behave the same. The owned/child window is always on top of the parent/owner.
If you want to break this, you have to break the owner/child relation. Let all dialog windows have no owner... than they may float freely.
But: I will expect the you program doesn't behave better. Even worse. User might search windows that are deep below covered under other windows. And they will never get in front, when your program gets active.
See the description about parent/child/owned windows here. Also this article might be helpful.
Edit: The problem is that internally the MFC sets the main window as an owner if no parent is given. Only the call to BOOL Wnd::CreateDlgIndirect(LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd, HINSTANCE hInst) allows to leave pParentWnd NULL.
So you may create the window as normal, but use SetParent(NULL) after it was created. Again the MFC ASSERTs this. SO you may use the API function and the handle of your dialog.

Finding BUTTON of another program

I am trying to make my program click the button of another program. The part I am having issues with is identifying the handle of the button. This is the code I have so far:
BOOL CALLBACK EnumChildProc(HWND windowHandle, LPARAM lParam)
{
cout << "hwnd_Child = " << windowHandle << endl;
cout<<"WindowId: "<<GetWindowLongPtr(windowHandle, GWLP_ID)<<endl;
return TRUE;
}
HWND windowHandle = FindWindow(NULL, "nameOfProgramWindow");
EnumChildWindows(windowHandle, EnumChildProc, 0);
What is happening so far is that I find the handle of the parent window of the program. With that handle, I use the EnumChildWindows function to go through each of the child windows... and in 1 of those child windows I will have the window which contains 3 buttons, 1 of which is the button I want to press. In the callback function I need to put the GetWindowLongPtr() function to get the ID of the window I am looking for ... there is a problem though...
The problem is that each time I run this program again, the handle and the ID of the window which contains the buttons will change... So I can't use spy++ to get an ID to compare with the ID gotten since The ID changes. I have tested it out even (thats why i have all the "cout" code there);
Question: How do I then identify the window I am looking for in the callback function (and possibly the button I am looking for)?? PLEASE DON'T say spy++ cause the ID and Handle values change every time I open the program
The handle is always going to change each time that the code runs. Windows dynamically assigns handles. Nothing you or anyone else can do about that. It would be more surprising if it didn't change.
And it's no particular conspiracy that the control's ID changes. The only way that stays the same is if it's hard-coded in the original application, most probably via the use of a resource file. But if the programmer dynamically generates the controls at run-time, then there's no reason they would need to use the same ID. Just so long as they keep track of that ID in some kind of data structure if and when they need it.
You may be able to find another property of the button control that is constant—like the caption. But that's certainly not guaranteed. Lots of programs change the caption to reflect the current state.
The application developer is under no obligation to make it easy for another program to mess with the internals of something that does not belong to them.
Have you considered doing this the right way using UI Automation?

Why CreateDialog failed with error code 5 in BHO?

I use CreateDialog to create a modeless dialog in SetSite method after get the IWebBrowser2 interface. The dialog resource is in the BHO dll. When create a new instance (I mean doble click the IE shortcut) of IE the creation is successful but when I create a new tab the creation is failed (but in other computer it is successful). there is also something strange is that sometimes create a new tab will also create a new IE process but sometimes will not.
This is the code for dialog creation:
bool MyDialog::Create(MyContext *ls)
{
extern HINSTANCE hInstance; // handle of BHO dll
m_hDialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MYDLG),ls->GetBrowserMainWnd(), MyDlgProc);
if (m_hDialog) {
SetWindowLong(m_hDialog, GWL_USERDATA, (LONG)ls);
SetTimer(m_hDialog, 1, 1000, NULL);
return true;
}
return false;
}
I think this has something to do with dialog creation in different UI thread but not sure about this. Hope somebody can help me with this problem. Thanks a lot!
Update 2014-03-31:
The GetBrowserMainWnd method call IWebBrowser2->get_HWND to get the main window handle. But for IE7 and later the introduced tabbed window makes things complex as MSDN's description:
"Internet Explorer 7. With the introduction of tabbed browsing, the return value of this method can be ambiguous. To alleviate confusion and maintain the highest level of compatibility with existing applications, this method returns a handle to the top-level window frame, not the currently selected tab."
So, I use the example code (refer to http://msdn.microsoft.com/en-us/library/aa752126(v=vs.85).aspx) solved this problem.
It seems that the root cause is the third parameter hWndParent. When I set it to NULL this problem disappear. I think the new process of an IE tab can't access the IE main window handle so failed with error code 5.

Should a CDialog based app set AfxGetApp()->m_pMainWnd

EDIT:
I Need to Research some weird stuff first, is there some way to put the question "on hold"?
Original:
I am working on an existing codebase using a CDialog based GUI. The Application consists of a CDialog "MainWindow", which spawns other CDialog "SubWindow"s using CDialog.DoModal.
This does work, when showing the SubWindow, the MainWindow blocks, etc.
When we call AfxMessageBox from "SubWindow", the MainWindow gets reenabled and focused.
Debugging into AfxMessagebox shows, that the function gets the mainWindow and reenables it. This causes a lot of different bugs. Using ::MesageBox works correctly, but we have about 50 different SubWindows, and, if possible, i would like to make only small, localized changes.
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc\appui1.cpp
int CWinApp::ShowAppMessageBox(CWinApp *pApp, LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
...
HWND hWndTop;
HWND hWnd = CWnd::GetSafeOwner_(NULL, &hWndTop);
// re-enable the parent window, so that focus is restored
// correctly when the dialog is dismissed.
if (hWnd != hWndTop)
EnableWindow(hWnd, TRUE);
...
In our entry Point we do something like this:
::AfxGetApp()->m_pMainWnd = &mainDlg;
mainDlg.DoModal();
What is the prefered way? Should i comment the line so the member stays NULL?
or could that cause any side effects?
I guess (not yet tested) i could also set
AfxGetApp()->m_pMainWnd = &subDlg;
prior to subDlg.DoModal() and reset it afterwards, but that would also mean changing 50 different files, one for every SubWindow.
Does MFC depend on m_pMainWnd, or should i just let it stay at NULL?
Thanks.
EDIT:
I tried passing the MainWindow to the SubWindow's constructor, but to no avail.
This is, where MainWindow gets reenabled:
This is, where MFC finds the MainWindow:
Do i need to manually set m_pActiveWnd ?
(OFF Topic: I love that there is the source code for mfc available.)
EDIT 2:
The MFC App is actually a DLL, which can be invoked in two ways:
Either loaded by a simple loader.exe, or by anyther big application.
This other application may also use MFC, so there may be two different CWinApp objects.
If it is loaded by loader.exe, the error does not occur.
MFC depends on m_pMainWnd in a lot of cases. Leaving it NULL isn't a good approach and it doen't fix your problem.
The main problem seams to be more subtle. The question is why does AfxMessageBox find the main dialog as the last active and not you subdialog. And this can be only a problem if you don't define a pParent when you create a new subdialog based con CDialog.
Try to pass the dialog that is currently active to the sub dialog you are calling. CDialog find the parent "automatically". But sometimes it doen't worked for me.
I had the same problem that the wrond dialog was enabled again after a message box or DoModal.
I fixed it, in defining always the parent when I create the sub dialogs.

Creating multiple dialogs in an MFC app with no main Window, they become children of each other

(title updated)
Following on from this question, now I have a clearer picture what's going on...
I have a MFC application with no main window, which exposes an API to create dialogs. When I call some of these methods repeatedly, the dialogs created are parented to each other instead of all being parented to the desktop... I have no idea why.
But anyway even after creation, I am unable to change the parent back to NULL or CWnd::GetDesktopWindow()... if I call SetParent followed by GetParent, nothing has changed.
So apart from the really weird question of why Windows is magically parenting each dialog to the last one created, is there anything I'm missing to be able to set these windows as children of the desktop?
UPDATED: I have found the reason for all this, but not the solution. From my dialog constructor, we end up in:
BOOL CDialog::CreateIndirect(LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd,
void* lpDialogInit, HINSTANCE hInst)
{
ASSERT(lpDialogTemplate != NULL);
if (pParentWnd == NULL)
pParentWnd = AfxGetMainWnd();
m_lpDialogInit = lpDialogInit;
return CreateDlgIndirect(lpDialogTemplate, pParentWnd, hInst);
}
Note: if (pParentWnd == NULL)pParentWnd = AfxGetMainWnd();
The call-stack from my dialog constructor looks like this:
mfc80d.dll!CDialog::CreateIndirect(const DLGTEMPLATE * lpDialogTemplate=0x005931a8, CWnd * pParentWnd=0x00000000, void * lpDialogInit=0x00000000, HINSTANCE__ * hInst=0x00400000)
mfc80d.dll!CDialog::CreateIndirect(void * hDialogTemplate=0x005931a8, CWnd * pParentWnd=0x00000000, HINSTANCE__ * hInst=0x00400000)
mfc80d.dll!CDialog::Create(const char * lpszTemplateName=0x0000009d, CWnd * pParentWnd=0x00000000)
mfc80d.dll!CDialog::Create(unsigned int nIDTemplate=157, CWnd * pParentWnd=0x00000000)
MyApp.exe!CMyDlg::CMyDlg(CWnd * pParent=0x00000000)
Running in the debugger, if I manually change pParentWnd back to 0 in CDialog::CreateIndirect, everything works fine... but how do I stop it happening in the first place?
Some thoughts:
First, you are passing NULL for the parent window the whole way through the chain. Its becomming non NULL when MFC tries to find your applications main window.
As I see it you have two mitigations:
Create a CWnd from the desktop window. CWnd::GetDesktopWindow will give you a non NULL window to use as a parent window that will inhibit the AfxGetMainWnd call.
Or trace into AfxGetMainWnd, find out where it is reading the main window from, and see if there is some setting to prevent it finding your dialog window.
On a final note. The MFC terminology is unfortunate :- On Windows, only child windows have parent windows. Popup or desktop windows have owner windows. CreateWindow takes a single parameter that accepts the owner, or parent of the window being created. The distinction is important because while a parent window can be changed, an owner cannot. SetParent will NOT change the owner window for a popup or overlapped window.
OK, found it!
There were actually two problems. I was passing NULL as the parent/owner... but trying to pass CWnd::GetDesktopWindow() was not helping so I'd given up on the idea until finding the behaviour of CDialog::CreateIndirect. That made me take a closer look at my code, and I finally spotted that MyDialog::MyDialog(CWnd *pParent) was calling super::Create(NULL), not super::Create(pParent)... because we'd always passed it NULL before anyway the bug had never been apparent.
So yet again, the hard problem turned out to be only one step away from a typo!
MFC can only create one window at a time IIRC. In the dark and distant past at least, when MFC creates a Win32 Window it needs to associate the MFC CWnd instance with the window. Because the first message a window receives is NOT the WM_CREATE message with the LPVUSERDATA parameter MFC stored the CWnd instance in a thread local variable - in the not unreasonable expectation that the next call to CWnd::WindowProc would be for the window it started trying to create.
I have no idea how one could actually write code to subvert this process. It all hinges on how you could be creating windows at differing velocities.