I have an OnMove handler in my dialog class, which does some stuff with control objects (i.e a CButton). I'm finding this handler gets called before the dialog OnInitDialog method is called, and hence when I try to call methods on the child controls, I get ASSERTS in debug as the controls don't yet exist... they are created in OnInitDialog.
There's two things I'd like to be able to check:
How do I tell the dialog has been initialized?
How do I check an individual CWnd control object's window has been created?
In both cases I'm looking for class members or method call results that can be used.
Set a flag in OnInitDialog
Use your dialog's m_hWnd:
if ( ::IsWindow(m_Ctrl.m_hWnd) ) {
...
}
Related
Using Visual Studio 2013, I created a dialog resource using the resource editor. It is a child control with no border and is just a collection of radio buttons, push buttons, and static text. I want to turn this into a custom control in order to place this in several different locations. Let's call this a "Panel".
I then created a regular dialog and using the Toolbox "Custom Control", defined an area for the Panel. The Panel registers itself and has a valid window handle.
I used the following example:
https://www.codeproject.com/Articles/521/Creating-Custom-Controls
The parent's DDX gets hit and the _panel is properly instantiated:
MyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX)
DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)
}
I read that I need to override the OnPaint() and OnEraseBkgnd(CDC* pDC) methods so the Panel class has these but they are empty. I do not have any custom painting to do as the Panel contains nothing but regular buttons.
What do I have to include in OnPaint()?
I also noticed that none of the member buttons are instantiated in the Panel like would normally happen in a dialog's DoDataExchange method. Instead, I've had to resort to dynamically creating each of the control's inside the Panel's PreSubclassWindow() method:
void MyPanel:PreSubclassWindow()
{
_groupBox.Create(_T("Options"), WS_CHILD|WS_VISIBLE|BS_GROUPBOX, CRect(11, 11, 112, 231), this, IDC_STATIC_GROUPBOX);
//... do this for every dialog element??? seems like overkill...
CWnd::PreSubclassWindow()
}
Why do I need to do this when I've already defined/designed the Panel and each of its controls in the resource editor?
If I do not do this in the PreSubclassWindow method, nothing will appear on the dialog.
Any help is appreciated. Thanks.
The article says override OnPaint and OnEraseBkgnd if you want to change the functionality. It doesn't say you have to override always.
Just remove ON_WM_PAINT and ON_WM_ERASEBKGND, remove OnPaint and OnEraseBkgnd if you don't need them. Or call the base class implementations if you are not making any changes:
void MyPanel::OnPaint() { CWnd::OnPaint(); }
BOOL MyPanel::OnEraseBkgnd(CDC* pDC) { return CWnd::OnEraseBkgnd(pDC); }
This will show a blank control with nothing in it, unless you add a child window to _panel as you have done in MyPanel:PreSubclassWindow
You are adding _groupBox to _panel. And you are adding _panel to the MyDialog.
MyDialog::DoDataExchange(...){DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)} is needed to invoke SubclassWindow for _panel. That in turn calls _groupBox.Create.
If MyPanel::OnPaint and MyPanel::PreSubclassWindow are not doing anything MyPanel appears as a blank control.
... do this for every dialog element??? seems like overkill...
You can directly add _groupBox to the main dialog. But if you want to add specific controls within MyPanel then you have to do it manually.
You can also create a child dialog within a main dialog. For example that's how a tab control works.
I have stuck with the following problem. I have a main dialog that has some controls. It also displays several subdialogs (as subcontrols) that are set as a pointer members with their IDs. I create these dialogs with their IDs via CreateControl during OnInitDialog. Yesterday it worked very well. Today, I decided to add some controls to subdialogs and caught a problem.
That's how I create subdialogs in the main dialog durig OnInitDialog:
if (m_pNewObjDlg != NULL) m_pNewObjDlg->Create(m_nNewObjDlgID, this);
if (m_pSharedObjDlg != NULL) m_pSharedObjDlg->Create(m_nSharedObjDlgID, this);
That's how I set the subdialogs outside from one of my routines:
dlgSelectSharedObject.SetNewPageObject(&dlgSelectNewTableDialog, CSelectNewTableDialog::IDD);
dlgSelectSharedObject.SetSharedPageObject(&dlgSelectSharedTable, CSelectSharedTableDialog::IDD);
I see that OnInitDialog and DoDataExchange of the two subdialogs work well, subdialogs' m_hWnds are set, GetDlgItem methods return valid values. But when the execution comes back to main dialog's OnInitDialog I see that m_hWnd values of pNewObjDlg and pSharedObjDlg are NULL. I don't know how that could happen. I double checked the IDs, they don't overlap. As a result I get an ASSERT with nothing displayed except main dialog with its controls. Just any thoughts?
The problem is solved. When data was loading to the controls within DoDataExchange I occasionally called Fail. It was a combo box without items because DoDataExchange was called from base class before combo box's initialization. It wasn't expected by the logic and thus Fail was called. It lead to the destruction of these child windows.
In my application I have a CFormView with a CTabCtrl, I also have 4 CFormViews that are children of the main CFormView and that are shown/hidden when the user changes the selected tab.
However, I can't find a way to make the Tab Order to work properly. If the CTabCtrl has the focus, pressing the Tab key has no effect and if one of the child CFormView has the focus the Tab key will move the focus only around the controls inside the CFormView.
I tried changing the z-order of the visible child CFormView to be right after the CTabCtrl with SetWindowPos, changed the child CFormViews styles to WS_EX_CONTROLPARENT but nothing seems to work.
You've started out from the wrong implementation: you shouldn't make a CFormView with a CTabCtrl and then stuff more CFormViews into it. This isn't going to work right. Instead, you should work with CPropertySheet and CPropertyPage, where focus handling has already been taken care of. You will still be able to access the CTabCtrl owned by the CPropertySheet by calling GetTabControl(), but MFC will take care of the problems you've encountered.
Briefly: derive classes from CPropertySheet for each of the dialog windows you want to show (e.g., CConfigPage1, CConfigPage2). Create a Dialog resource in the Resource Editor for each of them, and do all of the other standard CDialog setup.
Next, derive a class from CPropertySheet (e.g., CProps), and (optionally) handle WM_SIZE and TCN_SELCHANGE.
Finally, derive a class from a CView descendent, like CScrollView (e.g., CViewMyAwesomeStuff). Then add member variables for the CPropertySheet and CPropertyPages, and handle WM_CREATE where you Add() each page to the property sheet and then Create(this,WS_CHILD|WS_VISIBLE) the property sheet.
Bonus: You can forward the CView::OnUpdate to each child CPropertyPage by calling GetPage() in a loop and calling a function on each of them, or you can send a message to each of them (use a user-defined message, like WM_APP+1). They can discover their parent's CDocument by calling GetParent()->GetParent()->GetDocument().
I've used MFC dialogs before where you do:
EnterNameDlg dlg;
dlg.DoModal();
string str = dlg.GetName();
However a dialog I have now actually looks at a list-box control in such a method and it's not working. Although the class instance clearly exists after DoModal(), does the actual dialog get destroyed? I noticed calling DoModal() a 2nd time leads to OnInitDialog() also being called again which seems to support this theory, the dialog is recreated from the template rather than simply made visible the 2nd time.
Yes, DoModal creates a dialog on each call and destroys the window before returning.
Only the data members will still be valid. Of course, you can add more data members in your EnterNameDlg class if you want to collect data during dialog's lifetime.
As soon as the dlg gets out of scope, everything will be deallocated.
After DoModal class instance still exists, but window and all its controls are destroyed. You can call only functions that don't work with dialog controls after DoModal. Inside of the dialog class, keep required values in class members like CString, when OK button is pressed. These members can be read after dialog is closed.
The entirety of MFC is built around an awkward pairing - the Windows window with its associated handle, and the MFC class object. The MFC classes are designed to outlast the window in most cases.
I have a first dialog with a simple button on it and while clicking the button, a second dialog is created using CDialog::Create(IDD,this). I would like the parent to be notified when the second dialog is destroyed but without adding any code to the second dialog i.e., without adding a m_pParent->Notify() line in OnDestroy method.
I have tried OnParentNotify, PreTranslateMessage, SubclassWindow in the parent dialog with no success. I have not used the WS_CHILD style for the second dialog. Any idea?
To complete: in fact, I have a ComboBox derived class (but the issue is the same with buttons) and I'm displaying a modeless Dialog instead of displaying the listbox. But I would like the control to be as generic as possible so that any modeless dialog could be used. That's why I do not want to add a specific notification in the second dialog. If I'm obliged, I will use this trick but I asked for a more generic solution. PreTranslateMessage only catches WM_PAINT, WM_NCMOUSELEAVE and WM_NCMOUSEMOVE.
Use a base class and have your parent refer to the modeless child by base class only. In the base PostNcDestroy have it post to the parent.
It doesn't make sense to have the parent do a bunch of filtering / spying on all messages. It does make sense to implement behavior in a base class that you want to have common to all the different future flavors you might have of the modeless child.
OnParentNotify() is not called since dialog2 is not a child of dialog1.
PreTranslateMessage() should help here (although I don't like this bullet). The trick is that a modeless dialog doesn't destroy itself when it's closed. If you want the dialog to die, it must call DestroyWindow() when it closes, such in an OnCancel() override.
Of course, the first thing that comes to mind is t wonder why you don't want to add custom notification in your modeless dialog code.
EDIT: Another method would consist in installing a message hook (for the current thread, Not the whole system!). This would help you catch all messages for all windows associated to the same thread as dialog1. See SetWindowsHookEx()
How about posting a main parent form event to the message queue?