mfc access to formview item from dialog box - c++

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();

Related

How to change MFC View by clicking a Button inside the MainFrame

I want to change the presented View by clicking a button inside the Window
like this.
My Project settings:
I made an MFC Project (SDI) without Doc/View support.
I made two more Views in the Designer and added Classes to them. The new View Classes are derived from CFormView. I changed the Constructor and Destructor of the new View Classes to public.
Added them as pointers to MainFrm.h:
CMainView* m_pMainView;
CSecondView* m_pSecondView;
I changed the OnCreate(),OnSetFocus() and OnCmdMsg() Method of MainFrm.cpp like this:
(That allows to present the FormView I made with the Designer)
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
// First, build the view context structure
CCreateContext ccx;
// Designate the class from which to build the view
ccx.m_pNewViewClass = RUNTIME_CLASS(CMainView);
// Using the structure, create a view
m_pMainView = DYNAMIC_DOWNCAST(CMainView, this->CreateView(&ccx));
if (!m_pMainView)
{
TRACE0("creation of view failed");
}
// Do layout recalc
RecalcLayout();
// Show the view and do an initial update
m_pMainView->ShowWindow(SW_SHOW);
m_pMainView->OnInitialUpdate();
// Set this view active
SetActiveView(m_pMainView);
// Order it to resize the parent window to fit
m_pMainView->ResizeParentToFit(FALSE);
return 0;
}
...
void CMainFrame::OnSetFocus(CWnd* /*pOldWnd*/)
{
m_pMainView->SetFocus();
}
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (m_pMainView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
return TRUE;
return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
Now here comes my problem! I have a button on the First presented View and if you click on it, the view should change. I made the following function with the event handler in the Designer:
void CMainView::OnBnClickedButton1()
{
// What to do here? I want to change the current view to another View by clicking the button
}
If i handle it in the MainFrm.cpp class for example with menue buttons it is no problem... that works fine:
void CMainFrame::OnViewNextview()
{
CCreateContext ccx2;
ccx2.m_pNewViewClass = RUNTIME_CLASS(CSecondView);
m_pSecondView = DYNAMIC_DOWNCAST(CSecondView, this->CreateView(&ccx2));
RecalcLayout();
m_pMainView->ShowWindow(SW_SHOW);
m_pMainView->OnInitialUpdate();
SetActiveView(m_pMainView);
m_pMainView->ResizeParentToFit(FALSE);
}
I tried to write a function in CMainFrame and call this function in CMainView::OnBnClickedButton1() but I don't know how to get the current MainFrm Object. A pointer on MainFrm or a member of it in CMainView did not work.
I searched and red tutorials for days to solve my problem. I also tried it with Doc/View support like shown here:
https://learn.microsoft.com/en-us/cpp/mfc/adding-multiple-views-to-a-single-document?view=vs-2019 but i dont know where to call switchView() correctly.
Maybe anyone can help...
First, you shouldn't really be overriding OnCmdMsg - instead, use DECLARE_MESSAGE_MAP in your header file and BEGIN_MESSAGE_MAP/END_MESSAGE_MAP in your implementation file, and insert handler messages between those two macros.
I see that you already have a handler in your CMainView class for the button click! From here, you should call the CMainFrame function to change to the next view - just as you do when the menu command is given (which you say works). Make that function public and give the MainView class access to a pointer to the main frame (or use AfxGetMainWnd() and cast it to a pointer of your class). Something like this:
void CMainView::OnBnClickedButton1()
{
AfxGetMainWnd()->PostMessage(WM_COMMAND, menuID); // ID of menu command that works!
}
Big hugs for Adrian, i got it to work!
I also added a third view successfully :)
It is very IMPORTANT , to HIDE the last shown Window if you want to implement more views. you can do it like:
void CMainFrame::OnView3()
{
CCreateContext ccx3;
ccx3.m_pNewViewClass = RUNTIME_CLASS(CThirdView);
m_pThirdView = DYNAMIC_DOWNCAST(CThirdView, this->CreateView(&ccx3));
RecalcLayout();
m_pSecondView->ShowWindow(SW_HIDE); // Hide the last Window
m_pThirdView->ShowWindow(SW_SHOW); // Show the new Window
m_pThirdView->OnInitialUpdate();
SetActiveView(m_pThirdView);
//m_pThirdView->ResizeParentToFit(FALSE); //if you call this, the size of the window is the same like in the Designer
}

MFC Send message to a button (child to parent)

I want to send a message from my child window (CDialog) to the parent window (CFormview). If I press the cancel button at the child window, the Dialog should quit and the program should continue with the code of the STOP-Button at the parent Window.
void ChildDialog::OnBnClickedCancel()
{
CDTParentView *pButtonWnd = (CDTParentView *)AfxGetMainWnd();
pButtonWnd->OnBnClickedbuttonStop();
CDialogEx::OnCancel();
}
but there is an error in this objore.cpp:
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
ENSURE(this != NULL);
// it better be in valid memory, at least for CObject size
ASSERT(AfxIsValidAddress(this, sizeof(CObject)));
// simple SI case
CRuntimeClass* pClassThis = GetRuntimeClass(); <------- error
ENSURE(pClassThis);
return pClassThis->IsDerivedFrom(pClass);
}
Can anyone tell me, whats the problem?
And maybe post a better idea to send the button-clicked message?
Your code isn't actually sending a message, it's trying to call the handler directly. It's easy to simulate the clicking of a button the same way Windows would do it, then your existing code will handle it naturally.
CWnd * pMain = AfxGetMainWnd();
CWnd * pButton = pMain->GetDlgItem(ID_STOP_BUTTON);
pMain->PostMessage(WM_COMMAND, MAKEWPARAM(ID_STOP_BUTTON, BN_CLICKED), (LPARAM)pButton->m_hWnd);
AfxGetMainWnd does not return a pointer to the CFormView, it returns a pointer to the CMainFrame. If your dialog is modal you can simply check the return value of the DoModal call that displays the dialog. Or you might have better luck with calling GetParent to get a pointer to the CFormView.

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.

Disabling dialog OK button MFC

How do I disable MFC dialog OK button?
This code:
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
causes exception "Access violation reading location..."
in line ASSERT(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL)); of function CWnd::EnableWindow(BOOL bEnable) in winnocc.cpp from mfc90d.dll
In this time focus is on another control.
What's can be wrong?
Thanks for help.
[EDITED]
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
}
else
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(TRUE);
}
}
I'm not sure why would wouldn't be able to do it. If I take a regular CDialog and I do an init like this:
BOOL CMyDialog::OnInitDialog() {
CDialog::OnInitDialog();
CWnd *okbtn = GetDlgItem( IDOK );
if ( okbtn ) {
okbtn->EnableWindow( FALSE );
}
return TRUE;
}
it disables the button just fine. Perhaps something else is wrong?
Try this: http://support.microsoft.com/kb/122489
How to Disable Default Pushbutton Handling for MFC Dialog
Although default button (pushbutton) support is recommended, you might
want to disable or modify the standard implementation in certain
situations. You can do this in an MFC application by following these
steps:
Load the dialog into App Studio and change the OK button identifier
from IDOK to something else such as IDC_MYOK. Also, clear the check
from Default Button property.
Use ClassWizard to create a message
handling function for this button named OnClickedMyOK. This function
will be executed when a BN_CLICKED message is received from this
button.
In the code for OnClickedMyOK, call the base class version of
the OnOK function. Here is an example:
void CMyDialog::OnClickedMyOK()
{
CDialog::OnOK();
}
Override OnOK for your dialog, and do nothing inside the function. Here is an example:
void CMyDialog::OnOK()
{
}
Run the program and bring up the dialog. Give focus to a control other
than the OK button. Press the RETURN key. Notice that CDialog::OnOK()
is never executed.
I suspect the problem comes from pDlg pointer. When you call pDlg->GetDlgItem(IDOK), is the dialog already created already?
Make a breakpoint at the line CWnd* fieldOK = pDlg->GetDlgItem(IDOK); and debug into it to see if fieldOK pointer is null or a valid pointer.
That is why I think mark's answer is very close. You can disable it onOnInitDialog` or other members of you dialog class after it showed up.
The problem you have is that the button control has not been created on the interface yet. We do not get the full vision of your problem.
Anyway, you should protect your code from crashing. It is better that your code does nothing than to crash the application. Restructuring it like this avoids the access violation problem due to the NULL pointer:
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
if (fieldOK)
{
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
fieldOK->EnableWindow(FALSE);
else
fieldOK->EnableWindow(TRUE);
}
}
You need to load a bitmap for the disable mode of the OK button in LoadBitmaps() function.