Search code examples
c++visual-studio-2010mfc

How to pass a data in the modeless dialog window in Windows Desktop App (MFC, C++)?


The situation is quite simple, yet I don't understand in quite well.

I have a WinMFC app, which is also a name of the solution project. I want to pass the data from Main dialog of the app (WinMFCDialog.cpp + WinMFCDialog.h) into the modeless custom dialog (CustomDialog.cpp + CustomDialog.h)

Modeless means that the CustomDialog is functioning as a separate window, without catching focus on itself, so I can interact with WinMFCDialog separately.

Here are the codes:

WinMFCDlg.h
#pragma once

class CWinMFCDlg : public CDialogEx
{
public:
    CWinMFCDlg(CWnd* pParent = NULL);   

    enum { IDD = IDD_WINMFC_DIALOG };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    


protected:
    HICON m_hIcon;

    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedButton1();
};
WinMFCDlg.cpp
#include "stdafx.h"
#include "WinMFC.h"
#include "WinMFCDlg.h"
#include "afxdialogex.h"
#include "CustomDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

    enum { IDD = IDD_ABOUTBOX };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    

protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

CWinMFCDlg::CWinMFCDlg(CWnd* pParent /*=NULL*/): 
    CDialogEx(CWinMFCDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CWinMFCDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CWinMFCDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BUTTON1, &CWinMFCDlg::OnBnClickedButton1)
END_MESSAGE_MAP()

BOOL CWinMFCDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }
    SetIcon(m_hIcon, TRUE);         
    SetIcon(m_hIcon, FALSE);        

    return TRUE;
}

void CWinMFCDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

void CWinMFCDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this);

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Нарисуйте значок
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

HCURSOR CWinMFCDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}


void CWinMFCDlg::OnBnClickedButton1()
{
    // Modeless dialog, in which I want to pass the data
    CustomDialog *pDlg = new CustomDialog();
    pDlg->Create(IDD_CUSTOM_DIALOG, this);
    pDlg->ShowWindow(SW_SHOW);
}
CustomDialog.h
#pragma once

class CustomDialog : public CDialog
{
    DECLARE_DYNAMIC(CustomDialog)

public:
    CustomDialog(CWnd* pParent = NULL);
    virtual ~CustomDialog();

    enum { IDD = IDD_CUSTOM_DIALOG };

protected:
    virtual void DoDataExchange(CDataExchange* pDX);

    DECLARE_MESSAGE_MAP()
};
CustomDialog.cpp
#include "stdafx.h"
#include "WinMFC.h"
#include "CustomDialog.h"
#include "afxdialogex.h"

IMPLEMENT_DYNAMIC(CustomDialog, CDialog)

CustomDialog::CustomDialog(CWnd* pParent /*=NULL*/)
    : CDialog(CustomDialog::IDD, pParent)
{

}

CustomDialog::~CustomDialog()
{
}

void CustomDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}


BEGIN_MESSAGE_MAP(CustomDialog, CDialog)
END_MESSAGE_MAP()
WinMFC.h (main app, just in case)
#pragma once

#ifndef __AFXWIN_H__
    #error "включить stdafx.h до включения этого файла в PCH"
#endif

#include "resource.h"

class CWinMFCApp : public CWinApp
{
public:
    CWinMFCApp();

public:
    virtual BOOL InitInstance();
    DECLARE_MESSAGE_MAP()
};

extern CWinMFCApp theApp;
WinMFC.cpp (main app, just in case)
#include "stdafx.h"
#include "WinMFC.h"
#include "WinMFCDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CWinMFCApp
BEGIN_MESSAGE_MAP(CWinMFCApp, CWinApp)
    ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()

// Craetion of CWinMFCApp
CWinMFCApp::CWinMFCApp()
{
    m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
}

// Single object CWinMFCApp
CWinMFCApp theApp;

// Initialization of CWinMFCApp
BOOL CWinMFCApp::InitInstance()
{
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);

    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();
    AfxEnableControlContainer();
    CShellManager *pShellManager = new CShellManager;
    SetRegistryKey(_T("Test MFC APP"));

    CWinMFCDlg dlg;
    m_pMainWnd = &dlg;
    INT_PTR nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
    }
    else if (nResponse == IDCANCEL)
    {
    }

    if (pShellManager != NULL)
    {
        delete pShellManager;
    }

    return FALSE;
}

I input the data into the edit box, which is created in WinMFCDlg:

here how it looks like

The data should be displayed after in CustomDialog in a static field element:

here how it looks like

So, I tried to use DDX/DDV functions to pass the data from edit text of the WinMFC dialog into the static text field of Custom dialog.

This is what I tried to do:

  1. I added the signature of the function below into the WinMFCh (not in the class, so, it's not a method)
void DDX_TextNotEmpty (CDataExchange* pDX, int nIDC, CString& value);
class CWinMFCApp : public CWinApp
{
// Class definition
}
  1. I wrote the realization of it in WinMFC.cpp
void DDX_TextNotEmpty (CDataExchange* pDX, int nIDC, CString& value)
{
    HWND hWndCtrl = pDX->PrepareEditCtrl(nIDC);
    if (pDX->m_bSaveAndValidate)
    {
        int nLen = ::GetWindowTextLength(hWndCtrl);
        ::GetWindowText(hWndCtrl, value.GetBufferSetLength(nLen), nLen+1);
        value.ReleaseBuffer();
        if (value.IsEmpty ())
        {
            AfxMessageBox (_T("Enter something in the text field."), MB_ICONSTOP);
            pDX->Fail ();   //Fail () sets the focus on to the edit control
        }
    }
    else { ::SetWindowText(hWndCtrl, value); }
}
  1. In the CustomDialog.h I added a public member, which should store the received data:
class CustomDialog : public CDialog
{
    DECLARE_DYNAMIC(CustomDialog)

public:
    CString m_string;
    CustomDialog(CWnd* pParent = NULL);

// ... Default stuff created by VS2010 ...
}
  1. After I modified default constructor to set the member value by default in CustomDialog.cpp
CustomDialog::CustomDialog(CWnd* pParent /*=NULL*/)
    : CDialog(CustomDialog::IDD, pParent), m_string(_T("default value"))
{

}
  1. I after try to call that function in CustomDialog.cpp
void CustomDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_TextNotEmpty(pDX, IDC_EDIT_DATA, m_string);
}

In the end I received an error:

here it is

Well, I don't know what actually I did wrong, so I hope that my question is not drastically stupid or annoying to ask, I just want to learn how to use this old but interesting stuff 😔.

Will be glad for any help.


Solution

  • ... Okay, this is what I figured out ...

    The problem was that I was calling DDX_TextNotEmpty(pDX, IDC_EDIT_DATA, m_string) in a dialog, which was not created yet (in my case in CustomDialog). Besides, I used it wrong as well 😄. This function is like a binding in Android development. By that I mean that it updates variable (m_string) when it got changed in UI. But, thanks to Constantine Georgiou, there is a built-in DDX_Text, So no need to use that function in WinMFCDlg.cpp. So now it looks like this:

    void CWinMFCDlg::DoDataExchange(CDataExchange* pDX)
    {
     CDialogEx::DoDataExchange(pDX);
     DDX_Text(pDX, IDC_EDIT_DATA, m_string);
    }
    

    So, in order to pass the data into the modal I need to put my string inside of the static text element in my modal dialog, which I do by pressing the button in the WinMFCDlg, also updating it from UI. In order to keep the only one modal dialog instead of opening it several times I added CustomDialog as a field in the WinMFCDlg class:

    class CWinMFCDlg : public CDialogEx
    {
       public:
        CustomDialog* m_ptrDialog;
        // Don't forget to delete it
        virtual ~CWinMFCDlg(){ delete m_ptrDialog; }
       // Other stuff...
    }
    

    After I initialize the pointer in the BOOL CWinMFCDlg::OnInitDialog() in WinMFCDlg.cpp like this:

    BOOL CWinMFCDlg::OnInitDialog()
    {
      CDialogEx::OnInitDialog();
    
      // ...default initialization...
    
      m_ptrDialog = new CustomDialog();
      m_ptrDialog->Create(IDD_CUSTOM_DIALOG, this);
      return TRUE;
    }
    

    And finally, in order to put my data into a modal dialog I simply press the button in the WinMFCDlg:

    void CWinMFCDlg::OnBnClickedButton1()
    {
       UpdateData(TRUE);
       // Modeless dialog
       m_ptrDialog->SetDlgItemTextW(IDC_STATIC, m_string);
       m_ptrDialog->ShowWindow(SW_SHOW);
    }
    

    ... and see the result: here it is

    Which is what I wanted! But not in the exact way that I wanted... Well, if I figure this 'automatic-update-that-I-want-to-get' out, I will add the solution here 😊.

    😁 I FIGURED IT OUT! 😁
    The key was in event, which EditControl can handle. There is two of them: EN_CHANGE and EN_UPDATE:

    • EN_CHANGE - The display is updated after text changed;
    • EN_UPDATE - Edit control is about to display altered text.

    Still don't quite understand what is might be the most suitable in my case, anyway, it works.

    Here is what I added in WinMFCDlg.cpp:

    void CWinMFCDlg::OnEnChangeEditData()
    {
        // TODO:  If this is a RICHEDIT control, the control will not
        // send this notification unless you override the CDialogEx::OnInitDialog()
        // function and call CRichEditCtrl().SetEventMask()
        // with the ENM_CHANGE flag ORed into the mask.
    
        // TODO:  Add your control notification handler code here
        UpdateData(TRUE);
        m_ptrDialog->SetDlgItemTextW(IDC_STATIC, m_string);
    }