Child Dialog - SetWindowTextA or SendMessageA crashes program - MFC - c++

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.

Related

MFC MDI Collecting control states for the "apply" button routine

It was mentioned in some of my other threads on my app on that my code was incorrect because the apply button is present. I understand that now. It was said to collect the controls and then when apply is hit to send the data.
I have now idea how to approach that. So for the sake of general education. I have 1 property sheet and 5 property pages. For the sake of just general controls in use. Between all the 5, there are only radio controls and buttons, no edit controls (yet).
Let's assume there are 2 radios buttons and 1 button on each page.. where page 1 is radio1, radio2, button 1 and page 2 is radio3, radio4, button2....and so on.
I know that when the user selects something like a button or radio that the IsModified(TRUE) needs to be called to enable the apply button from grayed to active.
What would the code look like to scan all the controls and then apply them? I've never done it and I can't seem to find an example that isn't already super busy to gain the understanding of how to do it.
Anyone have a tutorial or code snippet or build a primer from the controls that I described above that could demonstrate how to execute this?
Update:
Ok so I have the DDX variables added:
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Control(pDX, STYLE_3D_USER, m_style_3d);
DDX_Control(pDX, STYLE_FLAT_USER, m_style_flat);
DDX_Control(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_shared_h_scroll);
DDX_Control(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
DDX_Control(pDX, STYLE_3D_ONENOTE_USER, m_style_onenote);
DDX_Control(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
DDX_Control(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
DDX_Control(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
}
My radio selection look like:
void CSettingsUserTabs::OnBnClicked3dUser()
{
//AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_3D_USER);
UpdateData(TRUE);
}
void CSettingsUserTabs::OnBnClickedFlatUser()
{
// TODO: Add your control notification handler code here
//AfxGetMainWnd()->SendMessage(WM_COMMAND, STYLE_FLAT_USER);
UpdateData(TRUE);
}
..... and the rest of them....
So to me, when I click any radio button, I expect that it scans all of controls on that property page? If so, all the variables have the values..do I call IsModified(); to enable the apply button...which then executes all the radio values i.e. only one selected? Is that the flow?
Update 2:
So this is what my modal dialog code is in MainFrame.cpp:
void CMainFrame::OnSettingsTools()
{
SettingsSheet SettingsSheet(L"Application Settings");
CSettingsPowerUser pgePowerUser;
CSettingsToolbars pgeToolbars;
CSettingsTheme pgeTheme;
CSettingsUserTabs pgeUserTabs;
CSettingsReset pgeReset;
SettingsSheet.AddPage(&pgeToolbars);
SettingsSheet.AddPage(&pgeTheme);
SettingsSheet.AddPage(&pgeUserTabs);
SettingsSheet.AddPage(&pgePowerUser);
SettingsSheet.AddPage(&pgeReset);
INT_PTR nRet = -1;
nRet = SettingsSheet.DoModal();
// Handle the return value from DoModal
switch (nRet)
{
case -1:
AfxMessageBox(_T("Dialog box could not be created!"));
break;
case IDABORT:
// Do something
AfxMessageBox(_T("ABORT!"));
break;
case IDOK:
// Do something
OnUserTabStyles(1);
AfxMessageBox(_T("OK!"));
break;
case IDCANCEL:
// Do something
AfxMessageBox(_T("CANCEL"));
break;
default:
// Do something
break;
};
}
The routine to verify if any of the radios buttons are changed in SettingsTabs.cpp:
void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
BOOL IsChecked = nCmdID;
CheckRadioButton(STYLE_3D_USER, STYLE_3D_ROUNDED_SCROLL_USER, nCmdID);
UpdateData(TRUE);
m_tabCmdID = nCmdID;
SetModified();
}
What the member variables look like in SettingsUserTabs.cpp:
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, STYLE_3D_USER, m_style_3d);
DDX_Radio(pDX, STYLE_FLAT_USER, m_style_flat);
DDX_Radio(pDX, STYLE_FLAT_SHARED_HORZ_SCROLL_USER, m_style_flat_h_scroll);
DDX_Radio(pDX, STYLE_3D_SCROLLED_USER, m_style_3d_scroll);
DDX_Radio(pDX, STYLE_3D_ONENOTE_USER, m_style_3d_onenote);
DDX_Radio(pDX, STYLE_3D_VS2005_USER, m_style_vs2005);
DDX_Radio(pDX, STYLE_3D_ROUNDED_USER, m_style_3d_rounded);
DDX_Radio(pDX, STYLE_3D_ROUNDED_SCROLL_USER, m_style_3d_rounded_scroll);
}
What the constructor looks like in SettingsUserTabs.cpp:
CSettingsUserTabs::CSettingsUserTabs()
: CMFCPropertyPage(IDD_SETTINGS_TABS)
, m_style_3d(FALSE)
, m_style_flat(FALSE)
, m_style_flat_h_scroll(FALSE)
, m_style_3d_scroll(FALSE)
, m_style_3d_onenote(FALSE)
, m_style_vs2005(FALSE)
, m_style_3d_rounded(FALSE)
, m_style_3d_rounded_scroll(FALSE)
, m_tabCmdID(FALSE)
{
}
This issue I'm seeing now is when I try to use the member variable m_tabCmdID it is coming back to unknown identifier so I'm not sure why the member variable isn't be seen. I am was expecting to use it like OnUserTabStyles(m_tabCmdID); so that it would pass the argument of the selected button to the method OnUserTabStyles. For now I just dumped a 1 in there to see if the mechanism works. I just am not clear how to access the member variable from the SettingsUserTabs.cpp from the IDOK. What am I missing?
EDIT: The range of options are sequential in the resource.h as 200-207, that is something I'm aware of and I know many don't like range options as they can get corrupted...this is my code, so I have no worries about the range being messed with.
Update 3:
Ok, so I finally understand the mechanism that Constantine described with the help of:
https://helgeklein.com/blog/2009/10/radio-buttons-in-mfc-visual-studio-2008-c/
I didn't have the tab order right nor did I have the first control set to true for the group.
With that, I now get the values 0-7 mentioned in the button group when I debug as I click each radio button based on its position in the group from 0-7 i.e. 8 buttons. Here is what the code looks like now.
SettingsUserTabs.cpp:
CSettingsUserTabs::CSettingsUserTabs()
: CMFCPropertyPage(IDD_SETTINGS_TABS)
, m_style_tabs(FALSE)
{
}
void CSettingsUserTabs::DoDataExchange(CDataExchange* pDX)
{
CMFCPropertyPage::DoDataExchange(pDX);
DDX_Radio(pDX, STYLE_3D_USER, m_style_tabs);
}
void CSettingsUserTabs::OnTabRadioClicked(UINT nCmdID)
{
UpdateData(TRUE);
BOOL RadioValueSelected = m_style_tabs; // only here to see 0-7 value for debugging only, not needed, test only
SetModified();
}
The mainframe.cpp above (Update 2): void CMainFrame::OnSettingsTools() is still the same.
So now here is where my question still isn't clear, I call the domodal from mainframe.cpp, the member variable m_style_tabs is in SettingsUserTabs.cpp. When I try to access the member variable, it says unknown identifier when I try to do something like this after the domodal int temp = m_styles_tabs;. I have a this in the mainframe.cpp
void CMainFrame::DoDataExchange(CDataExchange* pDX)
{
// TODO: Add your specialized code here and/or call the base class
CMDIFrameWndEx::DoDataExchange(pDX);
}
I would expect that the member would be seen in mainframe.cpp so I can process it after the domodal which is what I thought the whole point of this is?
How do I access the member variable so I can do the OnApply to it? I think I almost got it, I am just unclear on how to execute the last few steps for the actual apply itself.
Update 4:
The reason I ask about the restart is when the user selects the tabs property page and choose 1 of 8 choices, that option is stored in the registry and read during the Oncreate and then uses that style. Since it is OnCreate I haven't found a way to "redraw" or use the new tab setting except to restart the app. So by doing the reg save > respawn > end old ...I would want the dialog to reopen at the same tab property page so the user can see what the changes are if the apply was selected...vs. some dialog that says "restart"..or whatever. Below is the code used to demonstrate how it is working now.
OutputWnd.cpp
int COutputWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDockablePane::OnCreate(lpCreateStruct) == -1)
return -1;
CRect rectDummy;
rectDummy.SetRectEmpty();
// Create User Define tab style:
int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
// If the key doesn't exist, UserTableStyle will be 0 or FALSE;
if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type
int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
if (!m_wndTabs.Create(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle), rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
else { // Default tabs style if Reg key does not exist i.e. new install/program reset
if (!m_wndTabs.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, 1))
{
TRACE0("Failed to create output tab window\n");
return -1; // fail to create
}
}
… rest of function....
Update 5:
Here is the Apply in use from SettingsUserTabs.cpp:
BOOL CSettingsUserTabs::OnApply()
{
// TODO: Add your specialized code here and/or call the base class
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
return CMFCPropertyPage::OnApply();
}
Update 6:
Everything to this point is working, I ran into a road block trying to apply lessons learned here and at this link:
MFC MDI Substituting a class member dynamically
What was done in this topic was for the OutputWnd pane which works brilliantly! My MDI opens a file and uses CTabView and using the link shown in Update 6 allowed me to change the tabs on boot. Now that I have the OutputWnd doing it with OnApply, I'm trying to apply it to the document view when a file is loaded. I'm running into an access violation when I call the new function I created in TrainView.cpp and calling it from UserSettingsTabs.cpp. I thought it was the static_cast operation, but even if I do a simple bold using GetControlTabs() that also crashes (Shown in the commented out code, was on boot, now in OnApply to test theory). So clearly I need to capture the MDI document but not sure how that is done. I thought it would be as simple as:
GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
But when that crashed with a Cx000000005 access violation, I knew something was wrong on my end. I can't modify the CTabView operation, so I'm looking to see if we can fix what I'm doing wrong to have the OnApply change the tab styles without restarting as done in the OutputWnd we just fixed.
So as it stands, the OnApply that is working and now modded to try an integrate the CTabView functionality:
SettingsUserTabs.cpp:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
((CMainFrame*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.RecalcLayout();
CTrainView* TrainTabs; // User Call from anywhere method
TrainTabs->TrainDocUpdateTabsControl();
}
return bResult;
}
I added the function CTrainView::TrainDocUpdateTabsControl() to update the tabs...the rest of the code is fully operation i.e. void CTrainView::OnInitialUpdate()
The TrainView.cpp:
IMPLEMENT_DYNCREATE(CTrainView, CTabView)
void CTrainView::OnInitialUpdate()
{
CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: TrainView--CTrainView::OnInitialUpdate()."));
// add views // cmb
AddView(RUNTIME_CLASS(CInformationView), AfxStringID(IDS_INFORMATION));
AddView(RUNTIME_CLASS(CChaptersView), AfxStringID(IDS_CHAPTERS));
// Nicely hack to access protected member
class CMFCTabCtrlEx : public CMFCTabCtrl
{
public:
void SetDisableScroll() { m_bScroll = FALSE; }
};
// One-Liner to Disable navigation control
((CMFCTabCtrlEx*)&GetTabControl())->SetDisableScroll();
GetTabControl().EnableTabSwap(TRUE);
GetTabControl().SetLocation(CMFCBaseTabCtrl::Location::LOCATION_BOTTOM);
//GetTabControl().SetActiveTabBoldFont(TRUE);
GetTabControl().EnableAutoColor(TRUE);
// Modify User Define tab style:
int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
// If the key doesn't exist, UserTableStyle will be 0 or FALSE;
if (UserTabStyle != FALSE && UserTabStyle <= 8) { // User selected tab style type
int EnumUserTabStyle = UserTabStyle - 1; // Fix enum if key doesn't exist.
GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
}
else { // Default tabs style if Reg key does not exist i.e. new install/program reset
GetTabControl().ModifyTabStyle(CMFCTabCtrl::STYLE_FLAT);
}
CTabView::OnInitialUpdate();
}
void CTrainView::TrainDocUpdateTabsControl()
{
CTabView::AssertValid();
GetTabControl().SetActiveTabBoldFont(TRUE); << CAUSES Cx000000005 ACCESS ERROR CRASH WHEN CALLED.
//int EnumUserTabStyle;
//int UserTabStyle = AfxGetApp()->GetProfileInt(_T("Settings"), _T("UserTabStyle"), 0); //Get value from registry
//((CMainFrame*)AfxGetMainWnd())->GetTabControl().ModifyTabStyle(static_cast<CMFCTabCtrl::Style>(EnumUserTabStyle));
}
Update 6 EDIT:
The tabview is created from Application.cpp like this:
//Load Train Template
m_pkDocTrainTemplate = new CMultiDocTemplate(
IDR_TRAIN, // Loads TRAIN operation
RUNTIME_CLASS(CTrainDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CTrainView));
AddDocTemplate(m_pkDocTrainTemplate);
I tried to cast the m_pkDocTrainTemplate as I thought that was the pointer? Since it is MDI, I wasn't sure if there was an index issue since there can be multiple docs open simultaneously?
Below is an example of an application I wrote some time ago. It's a simple "Settings" dialog. Unlike yours, this one is derived from CDialogEx. But as CPropertyDialog is derived from CDialog, these apply in your case too.
Using the Wizard, I added member variables to the dialog class, bound to the dialog controls. Choose "Value", rather than "Control" in the "Category" combo in the Wizard. These are declared in the class definition. For simplicity, I only show three. There is a CString, an int and a BOOL variable, bound to an edit, a combo-box (drop-down list) and a check-box control respectively.
class CSettingsDlg : public CDialogEx
{
.
.
public:
CString m_DBConn;
int m_DumpSQL;
BOOL m_bLineNums;
}
In the implementation, the Wizard has modified the constructor and the DoDataExchange() member functions:
CSettingsDlg::CSettingsDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CSettingsDlg::IDD, pParent)
, m_DBConn(_T(""))
, m_DumpSQL(0)
, m_bLineNums(FALSE)
{
}
void CSettingsDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_DBCONN, m_DBConn);
DDV_MaxChars(pDX, m_DBConn, 255);
DDX_CBIndex(pDX, IDC_COMBO_DUMPSQL, m_DumpSQL);
DDV_MinMaxInt(pDX, m_DumpSQL, 0, 2);
DDX_Check(pDX, IDC_CHECK_LINENUMS, m_bLineNums);
}
The values in the constructor are the initial (default) ones. The DoDataExchange() function calls the DDX/DDV routines. The DDX routines perform the transfer of data (controls<->variables), while the DDV ones the validation - they are optional. The DoDataExchange() function is called by UpdateData(). Also, the default implementation of OnOK() calls UpdateData(TRUE), and if successful closes the dialog.
You will need to enable the Apply button if something has been modified. You can capture notification messages like EN_CHANGE, EN_UPDATE, BN_CLICKED etc (add an event in the property editor) and call the SetModified() function - this can prove quite tedious, but I can't see any other way to do it.
Such a dialog class can be used in the application as shown below:
void CChildView::OnSetoptions()
{
// Create a Settings-dialog class instance
CSettingsDlg sd; // Main application window as parent - will block every UI item in the application
// Set initial values for the member variables
sd.m_DBConn = szDBconn;
sd.m_DumpSQL = nDumpSQL;
sd.m_bLineNums = bDumpLineNums;
if (sd.DoModal() == IDOK)
{
// Store the values entered by the user
lstrcpyn(szDBconn, sd.m_DBConn, MAX_PATH);
nDumpSQL = sd.m_DumpSQL;
bDumpLineNums = sd.m_bLineNums;
}
}
EDIT :
I have a usage example in the code section just above. The procedure is create an instance of the dialog class, set the member variables' values (initial values, eg read from the registry), call DoModal() and if successful store (copy) the variables somewhere else (if not discard them). This should be done in some event handler, like CMainFrame::OnSettingsTools() in Update2. The CMainFrame::DoDataExchange() override makes no sense.
It's quite strange that you can't access the m_style_tabs variable. Isn't it a non-static, public member of the CSettingsUserTabs class? It should be declared in SettingsUserTabs.h. Won't it work if you acceess it as pgeUserTabs.m_style_tabs? In the CSettingsUserTabs class functions it can accessed simply as m_style_tabs. Also I see it is initialized as FALSE in the constructor. Is it a BOOL and not an int? (btw BOOL is defined as int in Win32, so the compiler won't complain) But the Wizard generates an int variable for radio-buttons, optionally with range validation as well.
Another point, you don't normally need to call UpdateData(TRUE) in CSettingsUserTabs::OnTabRadioClicked(). Please leave only the SetModified() call there. UpdateData(TRUE) is typically called in the OnOK() function. And usually you don't need to override these, because the default implementation is sufficient. The CPropertyPage documentation btw mentions that The default implementation of OnApply calls OnOK.
EDIT 2 :
In OnApply() you should first check if validation was successful. Also, OnCreate() isn't a "method" that can be called directly. It should be considered an "event". It's called by the framework when a window is created. You should instead call Create(). In your case you can destroy the Output Window and create it anew (with the new style). But, I see that the CMFCTabCtrl class has a ModifyTabStyle() function, which you can try calling, (without destroying the windows and creating it again). So, your code would become:
BOOL CSettingsUserTabs::OnApply()
{
BOOL bResult = CMFCPropertyPage::OnApply();
if (bResult)
{
AfxGetApp()->WriteProfileInt(_T("Settings"), _T("UserTabStyle"), m_style_tabs); // Save value to registry
((CMainFraime*)AfxGetMainWnd())->m_wndOutput.m_wndTabs.ModifyTabStyle((CMFCTabCtrl::Style)m_style_tabs);
}
return bResult;
}
The above code won't compile, because the m_wndOutput and m_wndTabs members are protected. You will have to make them public.

Create MFC controls in CDialog constructor [win32/MFC]

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.

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.

mfc access to formview item from dialog box

In my SDI application i need to get this behawiour. After I click on a button on the FormView, a CDialog opens. When I press the OK button on the CDialog, I call a function of the FormView. I don't want to close the CDialog. I try to do it with modeless dialog, but when i call formview function from dialog, i can't access to formview's control, like it's lost hwnd; the error is can't read memory of m_hwnd, the hwnd is ???.
This is my code:
Open modeless dialog:
CCampiDlg *m_pDialog = NULL;
HWND hCampi = NULL;
// Invoking the Dialog
m_pDialog = new CCampiDlg;
if (m_pDialog != NULL)
{
BOOL ret = m_pDialog->Create(m_pDialog->IDD, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
m_pDialog->ShowWindow(SW_SHOW);
}
when i press the ok button in the dialog i do:
CEditorTxView pView;
box2 = (CEdit*)(GetDlgItem(IDC_CAMPI_BOX2));
box2->GetWindowTextW(campo);
pView.inserisciCampo(1, campo);
In inserisciCampo function in CEditorTxView (CFormView) i have to do operation with my control txtCtrl, but it's lost hwnd. The declaration of txtCtrl is in the CEditorTxView.h
CTx1 txtCtrl;
And initialize it in DoDataExchange function:
void CEditorTxView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
DDX_Control(pDX, IDC_TX1, txtCtrl);
}
Someone can help me plz?
I can give you two answers here:
How to do what you are asking (get access to a control of the CFormView from a modeless dialog)
How to solve your underlying problem (communicate changes in a modeless dialog to the owner view)
For the first one, you have to declare a pointer to the view in the dialog class and initialize it in the constructor of the view:
class CCampiDlg : public CDialog
{
public:
CCampiDlg(CEditorTxView* pView, CWnd*pParent = NULL) // Change declaration to add pointer to view
: m_pView(pView)
{
}
// ... Whatever
private:
CEditorTxView* m_pView;
}
Now in your button handler:
CEdit* box2 = (CEdit*)(GetDlgItem(IDC_CAMPI_BOX2)); // Why not use a control variable?
box2->GetWindowTextW(campo);
m_pView->inserisciCampo(1, campo);
This should do what you are asking for. However, it is the wrong way to do it.
The problem with this approach is that the dialog knows way too much about its parent. It knows it is of type CEditorTxView and that it has a member called inserisciCampo, that takes a number and some text.
It shouldn't know that much. In fact, knowing anything about it, other than it is of type CView or even CWnd, is too much.
If the dialog knows about the view, you can't reuse the dialog with other views, and anytime the view changes its representation (what now is a textbox may be a combobox in the future, for example) the dialog must change accordingly.
The solution would be to send a message to the parent, explaining what's happened. Then the parent (the view) should know haw to handle that event. For example:
class CCampiDlg : public CDialog
{
public:
CCampiDlg(CWnd*pParent = NULL) {}
protected:
OnOk()
{
CString campo;
c_CampiBox2.GetWindowText(campo);
GetParent()->SendMessage(UWM_CAMPO2_SET, 0, (LPARAM)&campo);
}
}
In the view:
// It can be ON_REGISTERED_MESSAGE:
ON_MESSAGE(UWM_CAMPO2_SET, OnCampo2Set)
//...
LRESULT CEditorTxView::OnCampo2Set(WPARAM, LPARAM lParam)
{
CString* s = (CString*) lParam;
inserisciCampo(1, *campo);
return 0;
}
Now, you have decoupled the view and the dialog. The dialog knows nothing about the view. You can change its type, change the representation, even make it a dialog, and you don't have to change anything in the dialog. And if you need that same modeless dialog somewhere else, you just drop it there, create a message handler in the parent, and voilà!
For further explanations and better examples, check these articles:
Dialog and control design (Your case is explained in the section "Notifications to the environment")
Message management
On Ok button click the below code is running:
CEditorTxView pView;
box2 = (CEdit*)(GetDlgItem(IDC_CAMPI_BOX2));
box2->GetWindowTextW(campo);
pView.inserisciCampo(1, campo);
Note that, you are creating the new pView in stack and it does't attach with any window. You are not actually referring the view that already created and launched your dialog acting a parent. Revisit the above code and try the get the view:
Try the below code, if it is not working (Google it)
CFrameWnd * pFrame = (CFrameWnd *)(AfxGetApp()->m_pMainWnd);
CView * pView = pFrame->GetActiveView();

C++/MFC Error accessing control's variable

I created a control's variable for CEdit:
class CGateDlg : public CDialog
{
...
public:
// here is my control's variable
CEdit m_edit_a;
// here I map variable to control
virtual void DoDataExchange(CDataExchange* pDX);
}
And this is how I map my variable to the control:
void CGateDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_A, m_edit_a);
}
This is how it works: user types some text into the edit box. Then he presses the "Reset" button which clears the edit box. This is a piece of code responsible for clearing edit box after clicking Reset button:
void CGateDlg::OnBnClickedReset()
{
// clear edit box
m_edit_a.SetWindowTextW(L"");
}
Application starts without any errors. I type some text into EditBox and hit "Reset" button. Then I get an error which leads me to winocc.cpp, line 245 (ENSURE(this)):
void CWnd::SetWindowText(LPCTSTR lpszString)
{
ENSURE(this);
ENSURE(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL));
if (m_pCtrlSite == NULL)
::SetWindowText(m_hWnd, lpszString);
else
m_pCtrlSite->SetWindowText(lpszString);
}
I think the problem is with the hWnd:
this 0x0030fa54 {CEdit hWnd=0x00000000} CWnd * const
but how to fix it ?
Everything works fine when I access my control's value using this:
CEdit *m_edit_a;
m_edit_a = reinterpret_cast<CEdit *>(GetDlgItem(IDC_EDIT_A));
m_edit_a->SetWindowTextW(L"");
What am I doing wrong ?
I can see two possibilities:
The control does not exist when the dialog starts. The first thing that CDialog::OnInitDialog will do is call DoDataExchange, so if you're creating the control later in the initialization process it's too late.
Your own OnInitDialog is not calling CDialog::OnInitDialog so DoDataExchange is not being called.
I think you should no use directly the meber of your control (in this case m_edit_a). Instead you should use a memeber variable, let's say CStrimg m_edit_data, and you should link it to the control:
DDX_Text(pDX, IDC_EDIT_A, m_edit_data); // as you did it in DDC_Cotrol
Now you can use directy the variable, but in order the control to be updated you should use the following code before using it:
UpdateData(true); // unlocks the control in a sense
m_edit_data = "this is my test";
UpdateData(false); // locks the control again (in a sense)
This is normal procedure in MFC :), hope I helped...
ohh... you should also add the control to String Table ... (let me know if you do not know)
I can not find something wrong with you. I Create a new project using VC6.0,and associate a variable to the Edit,just link you do. the exe operates normally.
class CEditTestDlg : public CDialog
{
// Construction
public:
CEditTestDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CEditTestDlg)
enum { IDD = IDD_EDITTEST_DIALOG };
CEdit m_Edit;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEditTestDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
......
.cpp
void CEditTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CEditTestDlg)
DDX_Control(pDX, IDC_EDIT1, m_Edit);
//}}AFX_DATA_MAP
}
void CEditTestDlg::OnBnClickedReset()
{
// TODO: Add your control notification handler code here
m_Edit.SetWindowText("tttt");
}
so,I think it is not a code problem.You had better try again.
If your dialog starts off calling CDialog::OnInitDialog() and your DoDataExchange starts off calling CDialog::DoDataExchange but still you have null hWnd pointers and get CNotSupportedException, make sure your resource (rc) file's dialog template includes all the controls (IDC_) and such you have in DoDataExchange.
Check for overriding definitions if using a DLL that also provides resources.