Create MFC controls in CDialog constructor [win32/MFC] - c++

I'm working on the library, that wraps some MFC classes and methods. I want the user to be able to dynamically create a CDialogEx using a template in memory. For the modal dialogs, I call CDialog::InitModalIndirect and then CDialog::DoModal. For the modeless dialogs, I call CDialog::CreateIndirect and then CWnd::Show.
The code looks something like this:
// inside my library
class MyDialog : public CDialogEx
{
public:
MyDialog(CWnd* parent) : CDialogEx()
{
parent_ = parent;
my_template_data_ = CreateSomeGenericTemplate();
// OnInitDialog should be preferably called here
}
void ShowModal()
{
InitModalIndirect(my_template_data_, parent_);
DoModal(); // but it's called here - too late
}
void ShowModeless()
{
CreateIndirect(my_template_data_, parent_);
Show(); // but it's called here - too late
}
MyButton* GetButton(int id)
{
// returns the instance of my MyButton, which is a subclassed CButton
}
private:
BOOL MyDialog::OnInitDialog() override
{
CDialogEx::OnInitDialog();
// CWnd::Create for the UI controls can only be called here
}
};
// user's code
// user creates the dialog - in the constructor it's not clear if modal or modeless
1. MyDialog user_dialog(some_parent); // here, I need the controls to be created
2. user_dialog.GetButton(42)->SetWindowText(L"new text"); // user wants to initialize his controls
// but he can't, because MyButton::Create was not called yet
3. user_dialog.ShowModal(); // and only then display the dialog
//by default, here the MFC calls OnInitDialog - too late,
//the SetText method needed to be set on line 2.
My problem is, that the dialog's controls (buttons etc.) can only be created inside the CDialog::OnInitDialog method, which is called automatically after DoModal (for modal)/Show (for modeless) methods. I need the controls to be created and properly initialized (with the CWnd::Create method) preferably inside the constructor. I thought about calling Show/DoModal directly inside the constructor but I don't yet know if it's going to be modal or modeless dialog. It there a solution to this? Many thanks in advance.

Why don't you refactor your code and put the common code in a InitUI() method and call it from both sides?
And don't init interface on the constructor. Do it in OnInitDialog for modal; and in Create, or OnCreate (which implies a ON_WM_CREATE() map entry) for modeless dialogs.

Related

Accessing parent window from a dialog in mfc

I am making a doc/view arch SDI application.
I invoke a COptionsDialog in CSquaresView.
void CSquaresView::OnOptions()
{
COptionsDialog dlg(this);
if (dlg.DoModal() == IDOK)
...
}
In COptionsDialog I want to access CSquaresView.
BOOL COptionsDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CWnd *pParent = GetParent();
if (pParent) {
CSquaresView *pView = dynamic_cast<CSquaresView*>(pParent); //pView is always NULL
if (pView != NULL)
{
CSquaresDoc* pDoc = pView->GetDocument();
...
}
But I always get pView as NULL;
Please help me to solve this problem.
The observed behavior makes sense. A (modal) dialog's owner must be
an overlapped or pop-up window [...]; a child window cannot be an owner window.
CView-derived class instances generally are child windows. As such they cannot be the owner of a (modal) dialog. When you pass a child window into the c'tor of a CDialog-derived class, the system walks up the window hierarchy until it finds an overlapped or pop-up window, and uses that as the owner of the dialog. Regardless of whether you then call GetParent, GetAncestor, or CWnd::GetOwner, it is this true owner (usually your CFrameWnd-derived implementation) where window traversal starts.
Thus, you cannot generally use standard window traversal to find the window passed into a (modal) dialog's constructor. However, MFC records the CWnd(-derived) class instance you pass into your COptionsDialog constructor and stores it in a protected member variable m_pParentWnd, inherited from the CDialog class.
As long as COptionsDialog derives public/protected from CDialog or CDialogEx, the implementation can access this class member.
The following OnInitDialog implementation will do what you're looking for:
BOOL COptionsDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CSquaresView *pView = dynamic_cast<CSquaresView*>(m_pParentWnd);
if (pView != NULL)
{
CSquaresDoc* pDoc = pView->GetDocument();
...
}
There are other options available. For example, you could supply a COptionsDialog constructor that takes both a CWnd* and a CSquaresDoc*, delegating the first onto the base class c'tor and storing the document pointer in a (private) class member. This makes for code that's easier to follow in that it explicitly spells out, that the dialog depends on the document.
Add a static method GetCurrentView() to your view class:
.cpp file
CSquaresView* CSquaresView::GetCurrentView()
{
CFrameWnd* pFrame = (CFrameWnd*)(AfxGetApp()->m_pMainWnd);
return dynamic_cast<CSquaresView*>(pFrame->GetActiveView());
}
.h file
class CSquaresView : public CView
{
...
static CSquaresView* GetCurrentView();
...
};
Now you can just call GetCurrentView() from anywhere you want:
...
CSquaresView *pView = CSquaresView::GetCurrentView();
...
This works for SDI applications. For MDI applications the solution might be somewhat different.
Another thing you could do is just create a public member CSquaresView *m_pView in COptionsDialog and set that to this like:
void CSquaresView::OnOptions()
{
COptionsDialog dlg(this);
dlg.m_pView = this;
if (dlg.DoModal() == IDOK)
...
}
or modify the COptionsDialog constructor so m_pView is set directly by the constructor, or something similar.
Use GetOwner(). Only WS_CHILD windows have parents, other windows have owners.

How to Stop a CChildView Timer from Modeless Dialog

I have created a timer with SetTimer( 1 , 25 , 0 ); during CChildView::PreSubclassWindow().
In CChildView::OnTimer() I do InvalidateRect( 0 , 0 ); to cause an CChildView::OnPaint(). This causes a record from a file to be read and painted.
All of this works just fine.
During MyApp::InitInstance() I created a modeless dialog with several controls that all work as expected.
What I want to do is control the timer running in CChildView from a dialog control.
The problem is, I can't find or have access to the instance of CChildView.
Hence I can't call my SetTimerSpeed() function in CChildView.
How can I do this, please?
When creating the dialog store a pointer to your view in the dialog object. You can just use this one to access the view instance. Add a view-pointer member in your dialog class declaration:
class CMyDialog : public CDialogEx
{
public:
CChildView *pView;
.
.
}
And when calling it:
CMyDialog ctDlg;
ctDlg.pView = this;
ctDlg.DoModal();
// or ctDlg.Create();
The code above of course can be called from a handler (function) in CChildView.
If the application is SDI you don't even need this, you can just store the pointer to the view in a global variable, it is very much OK:
// Declaration in the .h file
extern CChildView *pChldVw;
// Definition in the .cpp file
CChildView *pChldVw;
// CChildView constructor
CChildView::CChildView() noexcept
{
pChldVw = this;
}
If the application is MDI and the dialog is not created in the CChildView code, it is still possible to find the view instance by calling something like:
CMDIFrameWnd *pFrame = (CMDIFrameWnd*)AfxGetApp()->GetMainWnd();
// Get the active MDI child window.
CMDIChildWnd *pChild = (CMDIChildWnd*)pFrame->GetActiveFrame();
// Get the active view attached to the active MDI child window.
CChildView *pView = (CChildView)pChild->GetActiveView();
There are also some "enumeration" functions, like:
GetFirstDocTemplatePosition() / GetNextDocTemplate()
GetFirstDocPosition() / GetNextDoc()
GetFirstViewPosition() / GetNextView()

MFC Edit control message handle access Document object

I create a new class extend CEdit to override some of the message handles.
My ultimate goal is when edit control is on focus, some of the toolbar buttons become available.
I created a bool variable in doc. then the pCmdUI->enable() set to this bool. The onfocus is overridden in new edit control class. I'm having trouble to update this bool vairbale from the onfocus message handle.
void CMFCDoc::OnUpdateTextColor(CCmdUI *pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(shape_onfocus_);
}
class CMFCDoc : public COleServerDoc
{
...
bool shape_onfocus_;
}
//edit control
#include <afxwin.h>
class CEditControl :
public CEdit
{
public:
CEditControl();
~CEditControl();
DECLARE_MESSAGE_MAP()
afx_msg void OnEnSetfocus();
};
void CEditControl::OnEnSetfocus()
{
//----- I want to update shape_onfocus_ here. -----
this->SetWindowTextA(_T("Hello world"));
}
Assuming your CEditControl instance is a child of some sort of CView, you could go about it like this:
void CEditControl::OnEnSetfocus()
{
CView *view = static_cast<CView *>(GetParent());
CMFCDoc *doc = static_cast<CMFCDoc *>(view->GetDocument());
doc->shape_onfocus_ = true;
...
}
Assuming the edit-control is a child of a CView-derived class, you should better put the OnUpdateUI() handler in the view class, not the document one.
For example, if the view-class is CFormView-derived (dialog), you could simply write:
void CMyView::OnUpdateTextColor(CCmdUI *pCmdUI)
{
pCmdUI->Enable(GetFocus()==GetDlgItem(IDC_MYEDIT));
}
This piece of code works for both SDI and MDI applications.
If the view class is not CFormView-derived (the edit-box was created programmatically), the code above could be modified slightly, and instead of calling GetDlgItem() you should enumerate the view's children list (search your edit-box there).
If the only reason to override the edit-control was to capture the EN_SET/KILLFOCUS messages, sorry this wasn't worth the effort, as you could simply capture these in the view's code. Then the view's message-map would contain:
ON_EN_SETFOCUS(IDC_MYEDIT, &CMyView::OnEnSetfocusMyEdit)
ON_EN_KILLFOCUS(IDC_MYEDIT, &CMyView::OnEnKillfocusMyEdit)
and the view-class code:
void CMyView::OnEnSetfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
void CMyView::OnEnKillfocusMyEdit()
{
// TODO: Add your control notification handler code here
}
These are generated by the wizard. Go to the Class View tab, select your class and then go to the Events page; in the Controls subtree you can find your control and add handlers for its events. But all this is not needed, as you can just use GetFocus()/GetDlgItem() as suggested above.
And as other members said, you can access the document class from any of its views by calling the GetDocument() function.

Apply button in CDialog

I have a dialog in which after pressing button OK, the program uses the data in the dialog and draws a plot. I need to draw the plot without having to close the dialog as with IDOK, hence the apply button.
The code with drawing the dialog is,
INT_PTR val = dlg->DoModal();
if ( val == IDOK) {
//draw plot
}
The code of onOK and onApply
void DLg::OnOK() {
GetDataGrid();
CDialog::OnOK();
}
void DLg::OnBnClickedApply()
{
GetDataGrid();
}
How do I get DoModal() to return a value on onApply() without closing the dialog?
Any help would be appreciated.
A modal dialog can't return a value and leave the dialog open. You could either make your dialog non-modal, or post your main window a message from the OnBnClickedApply function that makes it draw the plot.
I tend to put drawing into a separate thread and would call it wherever needed. So you can either
(1) call the OnDrawPlot again in your Apply button
if ( val == IDOK) {
AfxBeginThread(...);//draw plot
}
void DLg::OnBnClickedApply()
{
AfxBeginThread(...);//draw plot
}
(2) send the return value back to the DoModal using EndDialog method
What parameters are there in EndDialog ?
An example can be found here.
Declare a variable in CDialog derived class preferably public. Then just at OnOK assign this variable to appropriate value. The caller would use it directly.
class Dlg : public CDialog
{
public:
int TheVariable;
...
};
Call site:
if(dlg.DoModal()==IDOK)
{
dlg.TheVariable; // Use the variable
}
However, if you need to draw on the dialog itself (and not to other window, which has launched the dialog), then don't call CDialog::OnOK or EndDialog in your OnOK override. In this case, you need to do painting in dialog itself.

Child Dialog - SetWindowTextA or SendMessageA crashes program - MFC

ERROR: afxwin2.inl line 165
My app is a dialog box with a few edit boxes. Once I click the button to evaluate the information entered I want to open a child dialog to display the results. I tried overloading DoModal() like this:
//in the child dialog
//.h
CResultsDlg::CResultsDlg(CParentDlg *parent);
virtual INT_PTR DoModal(float bmi);
//.cpp
CResultsDlg::CResultsDlg(CParentDlg *parent) : CDialogEx(CResultsDlg::IDD), _parent(parent)
{ //initializations }
INT_PTR CResultsDlg::DoModal(float bmi)
{
m_sBMI.Format("%f", bmi);
m_hBMI.SetWindowTextA(m_sBMI); //crashes !!!!!!!!!!
m_hBMI.SendMessageA(WM_SETTEXT, 0, (LPARAM)"15.11"); //crashes !!!!!!!!
// OnInitDialog(); //because this wasn't getting called at all
return CDialogEx::DoModal();
}
BOOL CResultsDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// __super::OnInitDialog(); //no difference...
m_hBMI.SetWindowTextA("10.3"); //crashes !!!
return true; // return TRUE unless you set the focus to a control
}
//in the parent dialog
//.cpp
void CParentDlg::OnBnClickedCalculate()
{
CResultsDlg childResultsDlg = this;
childResultsDlg.DoModal(15.7);
}
m_hBMI is a handle to a static text control. I tested an edit box but it still crashed.
I understand that it probably has something to do with the controls not being created yet but I tried every way I know.
Using breakpoints, I confirmed that OnInitDialog does not get called at all unless I put it in the overloaded DoModal function. SetWindowText/SendMessage still crashes in OnInitDialog with the same ASSERT error.
If I remove all SetWindowText/SendMessage then the child window does come up modal like it should but the 'result' static text control is the same as the text I set it to in the dialog editor properties pane.
Thanks !!!!!
*MORE DETAILS*-----------
void CResultsDlg::DoDataExchange(CDataExchange* pDX) // DDX/DDV support
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_BMI, m_fBMI);
DDV_MinMaxFloat(pDX, m_fBMI, 0, 100);
DDX_Control(pDX, IDC_BMI, m_hBMI);
}
The usual sequence when you start a dialog is:
You call CDialog::DoModal.
The dialog window gets created.
The child controls of the dialog get created.
OnInitDialog gets called.
CDialog::OnInitDialog calls DoDataExchange.
You have a DDX_Control call in your DoDataExchange method to map the child control to a member variable.
Notice that the member variables only get initialized at the end of that sequence. You're trying to use them way before, so you get a crash.
Store the value you need for initialization in a member variable and take care of it in DoDataExchange.