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.
Related
I have a CFormView SDI which calls and opens up a CDialog.
In the CDialog I have a button which has a bitmap displayed.
All works well, until I close the CDialog.
When I open up the same CDialog (using create function), the button appears, and the button's functionality is there, however the bitmap disappears.
Can someone please tell me why the bitmap on the button disappears on subsequent calls to CDialog?
Thank you in advance.
Here is the code:
In the CFormView I have a button that creates the CDialog:
void CTest4View::OnButton1()
{
m_dialog_calculator.Create(IDD_DIALOG1, GetDesktopWindow());
m_dialog_calculator.ShowWindow(SW_SHOW);
}
In the CDialog I have the bitmap put on the button in the InitDialog:
BOOL CCalculator::OnInitDialog()
{
CDialog::OnInitDialog();
if(!bitmapNew.LoadBitmap(IDB_BITMAP_NEW)){
MessageBox("problem in loadbitmap");
}
if(!m_button.SetBitmap(bitmapNew)){
MessageBox("problem in SetBitmap");
}
bitmapOpen.LoadBitmap(IDB_BITMAP_OPEN);
m_buttonOpen.SetBitmap(bitmapOpen);
//==========================
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Upon further investigation, the problem seems to be in: m_button.SetBitmap(bitmapNew) since this returns FALSE. Can someone please help me?
Quick fix:
void CTest4View::OnButton1()
{
// only create the dialog once
if (m_dialog_calculator.m_hWnd==NULL)
m_dialog_calculator.Create(IDD_DIALOG1, GetDesktopWindow());
m_dialog_calculator.ShowWindow(SW_SHOW);
}
Additional information 1:
The information that OP provided in his question/code is quite little, so I actually have to recreate a similar test project to guess what is wrong with the missing bitmap. What I found is that the CDialog and CBitmap is being created multiple times when the button is pressed, this causes subsequent creation api call to fail, other than the first creation call. The result is some unexpected behavior as you can see now.
The program is supposed to generate some assertion errors when run in debug mode due to the creation failure but I guess the OP compiled it in release mode so all warnings are being suppressed.
The problem occurred because the calculator dialog is created as a modeless dialog as compared to a normal DoModal way of activation. The usual way of doing such modeless dialog is to create the dialog only once, by monitoring the m_hWnd member variable. When the user want the dialog to be dismissed, it is simply being hidden instead of being destroyed. This will avoid all the multiple creation problems altogether.
I guess presently, the calculator dialog is assumed to be closed and destroy by clicking the "X" button on the top right of dialog, well, actually it is only hidden but not actually being destroyed by the default handling of CDialog. The correct way to dismiss the modeless calculator dialog is thus to overide the OnClose event to hide it using ShowWindow(SW_HIDE). To activate it again, use ShowWindow(SW_SHOWNORMAL).
First of all, here's my config:
VS2010/Debug/C++ Win32 Project/Vista Home Premium
Invoking GetOpenFileName via a button (CreateWindow) in a window (CreateWindow) gets me no problem: The Open Dialog works fine, I can click, navigate to another folder etc...
Now, I replace my CreateWindow with a DialogBoxParam (that should call CreateWindow behind the scenes), with the same (DLGPROC)WndProc and invoke the same GetOpenFileName. Here, the Open dialog is behaving strangely: looks like only the mouse double-click works (= populating ofn.lpstrFile and closing the Open dialog). Not able to click the Open and Cancel buttons and not able to navigate.
Anyone having experienced this before? Any known reasons for the Open dialog to kind of "freeze". Belong to a parent or not (ofn.hwndOwner = hwnd; ofn.hwndOwner = NULL;) gives the same problem.
Thanks
N
You wrote
with the same (DLGPROC)WndProc
That's your bug. A dialog procedure and a window procedure are not the same thing. They follow different rules, and if you follow WndProc rules when you should be following DlgProc rules, then bad things will happen.
(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.
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.
I'm having problems with some code to create a CDialog based window. The code was working fine last week. The only changes I made was replacing a C++ deque with a hash array. I had commented out the line of code with the Create method being called to allow me to skip loading the window. Now the code doesn't create the window at all anymore.
The Create function returns false and the GetLastError function returns 0. I don't use any custome controls inside the window - just a checkbox and a list control. As far as I can tell (I can't hook a debugger up at this point) the OnCreate and OnInitDialog functions are not being called at all.
I've pasted the code below that I've been using to test the Create function's return and GetLastError
BOOL result = ORDER_HANDLER_GUI.Create(OrderHandlerGUI::IDD, AfxGetMainWnd());
int error = ::GetLastError();
if(result)
AfxMessageBox("Created GUI");
else
{
CString msg;
msg.Format("%d", error);
AfxMessageBox("Could not create GUI");
AfxMessageBox(msg);
}
Update:
I finally managed to get the debugger to attach (this is a plugin loaded in a 3rd party application that didn't like the debugger for some reason). After stepping through the code it seems that AfxGetMainWnd() is returning NULL. I'm doing more testing on this now.
The problems seems to have been with the call to CDynLinkLibrary().
I had commented this out at the request of the company that writes the software that is loading my plugin. Adding this line back in caused some values to still be null, but the window is now created properly.
I'm going to do a bit of research on this and will update if I find anything. If anyone knows more about this than me (not hard to do) feel free to leave comments.
Does the dialog use any controls that might be causing the problem? A richedit for example?