[MFC] Cum schimb date cu un dialog modal?

Despre MFC, ATL si alte biblioteci C++ de la Microsoft (forum moderat)
Post Reply
User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

[MFC] Cum schimb date cu un dialog modal?

Post by Ovidiu Cucu » 24 Dec 2008, 02:49

Intrebare
Cum setez in, respectiv cum extrag date dintr-un dialog modal?

Raspuns
Incercari de genul celor ca in codul mai jos...

Code: Select all

   CTestDialog dlg;
   dlg.SetDlgItemText(IDC_EDIT_TEST, strText);
   dlg.CheckDlgButton(IDC_CHECK_TEST, BST_CHECKED);
   //...
   dlg.DoModal();

   dlg.GetDlgItemText(IDC_EDIT_TEST, strText);
   UINT nCheck = dlg.IsDlgButtonChecked(IDC_CHECK_TEST);
   // ...
...esueaza pentru ca inainte de apelul functiei DoModal nu este creat inca dialogul deci nu exista nici controalele child, respectiv la iesirea din DoModal dialogul este deja distrus impreuna cu toti copii sai.
Rezolvarea cea mai simpla difera de la caz la caz.

Cazul 1
Daca este vorba doar de controale simple cum ar fi edit, check-box sau radio-button atunci ne vine in ajutor wizard-ul (ClassWizard in VS6.0 respectiv Add Member Variable Wizard in VS200x).
Atasam controalelor cate o variabila publica din categoria "Value". Suplimentar, wizard-ul va adauga in fiunctia DoDataExchange si cate o functie DDX (Dialog Data EXchange ) corespunzatoare (DDX_Text, DDX_Check etc).

Code: Select all

// TestDialog.h
class CTestDialog : public CDialog
{
// Dialog Data
    //{{AFX_DATA(CTestDialog)
    CString m_strTest; 
    BOOL    m_bTest;
    //}}AFX_DATA
//...

// TestDialog.cpp
void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CTestDialog)
    DDX_Text(pDX, IDC_EDIT_TEST, m_strTest);
    DDX_Check(pDX, IDC_CHECK_TEST, m_bTest);
    //}}AFX_DATA_MAP
}
Functia DoDataExchange este apelata de framework atat la creare cat si atunci cand dialogul este inchis setand datele in, respectiv extragand datele din controale. Deci in clasa dialogului modal nu mai avem nimic de facut.
Ramane doar sa facem "transferul" inainte si dupa DoModal.

Code: Select all

    CTestDialog dlg;
    dlg.m_strTest = strText;
    dlg.m_bTest = TRUE;
    //...
    if(IDOK == dlg.DoModal())
    {
       strText = dlg.m_strTest;
       BOOL bIsChecked = dlg.m_bTest;
       // ...
    }
Aplicatie demo
DemoModal1.zip
(9.86 KiB) Downloaded 385 times

Next FAQ >>



User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

[MFC] Cum schimb date cu un dialog modal? (2)

Post by Ovidiu Cucu » 24 Dec 2008, 14:03

Cazul 2
Avem controale ceva mai complexe cum ar fi list-box si combo-box.
In acest caz wizard-ul nu ne mai ajuta, asa ca va trebui sa punem manual in dialogul modal datele membru cat si functiile de umplere si extragere de date din control.
Sa luam de exemplu un dialog modal continand un check-list-box (implementat in clasa MFC CCheckListBox).
Definesc o structura care contine informatii despre un element al listei apoi declar o referinta la un array de asemenea structuri. Acesta va face legatura intre dialogul modal si fereastra parinte.

Code: Select all

struct CCheckListBoxItem  
{
   CString m_strText;
   int m_nCheck;
   // ...
};
typedef CArray<CCheckListBoxItem, CCheckListBoxItem&> CheckListBoxItemArray;

Code: Select all

class CTestDialog : public CDialog
{
   CCheckListBox m_listTest;
   CTestDialog(CheckListBoxItemArray& >& m_arrItems;
public:
   CTestDialog(CheckListBoxItemArray& arrItems, CWnd* pParent = NULL);
   // ...
};

Code: Select all

CTestDialog::CTestDialog(CheckListBoxItemArray& arrItems, CWnd* pParent)
   : CDialog(CTestDialog::IDD, pParent),
     m_arrItems(arrItems)
{
   //...
}
In clasa ferestrei parinte ramane sa pun doar

Code: Select all

   CTestDialog dlg(arrItems);
   dlg.DoModal();
Am lasat la urma functiile umplerea listei si de extragerea elementelor din controlul list-box.
De obicei se scriu doua functii, sa le zicem FillListBoxFromArray si FillArrayFromListBox, care se apeleaza din OnInitDialog si respectiv din OnOK.
O abordare ceva mai eleganta care ne permite sa beneficiem de framework este sa scriem o functie DDX specifica scopului nostru, pe care sa o plasam in DoDataExchange (vezi si comentariile despre DDX de la cazul 1).

Code: Select all

void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   // ...
   DDX_CheckListBox(pDX, IDC_LIST_TEST, m_listTest, m_arrItems);
}
Cam atat.
Un exemplu de implementare pentru DDX_CheckListBox il puteti gasi in aplicatia demo atasata aici.
DemoModal2.zip
(10.42 KiB) Downloaded 358 times

Next FAQ >>

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

[MFC] Cum schimb date cu un dialog modal? (3)

Post by Ovidiu Cucu » 24 Dec 2008, 16:04

Cazul 3
In mod normal un dialog modal, odata creat, nu da controlul parintelui pana cand utilizatorul nu-l inchide. Uzual, in MFC abia dupa ce se iese din functia DoModal se iau datele din el.
Exista totusi situatii in care se cere ca, de exemplu la apasarea unui buton "Apply", modificarile facute in dialogul modal sa se reflecte imediat in fereastra parinte.
Cum se rezolva aceasta?

Simplu, folosind o structura de date comuna dialogului modal si ferestrei parinte si un mesaj utilizator.

Code: Select all

enum e_APP_MESSAGES
{
   WM_APP_APPLYCHANGES = WM_APP,
   // ...
};

Code: Select all

class CTestDialog : public CDialog
{
public:
   CTestDialog(CData& data, CWnd* pParent);
// ...
private:
   CData& m_data;
   void DDX_Data(CDataExchange* pDX);
   afx_msg void OnButtonApply();
};

Code: Select all

// ...
CTestDialog::CTestDialog(CData& data, CWnd* pParent)
   : CDialog(CTestDialog::IDD, pParent),
     m_data(data)
{
   // ...
}
void CTestDialog::OnButtonApply() 
{
   UpdateData(); // Calls DoDataExchange to get data from controls
   CWnd* pWndOwner = GetOwner();
   ASSERT(NULL != pWndOwner); // This dialog MUST have an owner!
   pWndOwner->SendMessage(WM_APP_APPLYCHANGES);
}
void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
   // ...
   DDX_Data(pDX);
}
void CTestDialog::DDX_Data(CDataExchange* pDX)
{
   // see implemetation details in the demo application
};
In clasa ferestrei parinte tratez mesajul utilizator

Code: Select all

class CMainDialog : public CDialog
{
   // ...
   afx_msg LRESULT OnApplyChanges(WPARAM wParam, LPARAM lParam);
};

Code: Select all

//...
   ON_MESSAGE(WM_APP_APPLYCHANGES, OnApplyChanges)
END_MESSAGE_MAP()
// ...
LRESULT CMainDialog::OnApplyChanges(WPARAM wParam, LPARAM lParam)
{
   SetDataToDialog();
   return 0;
}
Aplicatie demo
DemoModal3.zip
(10.99 KiB) Downloaded 361 times

Next FAQ >>

User avatar
Andreas
Membru
Membru
Posts: 117
Joined: 09 Nov 2008, 12:13
Judet: Timiş
Location: Timisoara

Re: [MFC] Cum schimb date cu un dialog modal? (4)

Post by Andreas » 27 Dec 2008, 01:10

Pentru schimbul elegant de date intre un dialog modal si fereastra parinte, am imaginat o metoda pe cat de simpla pe atat, zic eu, de robusta, intrucat este cumva asemanatoare cu ceea ce se intampla intr-o aplicatie de tip server de automatizare, sa presupunem cu o singura fereastra dialog ca fereastra principala, unde clasa dialog si clasa proxy obtine fiecare cate un pointer la cealalta clasa. Deci am redus problema la cum ar fi posibil sa obtinem un pointer la clasa parinte care sa poata fi transmis (utilizat) in “bucla” unui dialog modal, astfel incat ferestra parinte sa reflecte imediat modificarile facute in dialog-ul modal.

Ideea este simpla:

1. Se declara si defineste la nivel global in fisierul de definitie a clasei dialog parinte o variabila de tip pointer la clasa dialog parinte:

Code: Select all

CDialogParinte* pProxyDialogParinte=NULL;
este importanta initializare pointerului, aici fiind si punctul de definire a acestuia. In alta parte a proiectului vom utiliza specificatorul “extern” pentru a obtine acelasi pointer.

2. In constructorul clasei dialog parinte atribuim pointerului de mai sus valoarea pointerului “this”:

Code: Select all

pProxyDialogParinte=this;
3. In fisierul de definitie al clasei dialog copil se obtine pointerul la clasa dialog parinte, folosind specificatorul “extern”:

Code: Select all

#include “DialogParinte.h”

extern CDialogParinte* pProxyDialogParinte;
Prin acesta vom avea acces “deplin” la toati membrii clasei dialog parinte, pe toata durata de viata a dialogului modal.

Demo:
ModalUsingProxy.zip
(67.13 KiB) Downloaded 374 times

Next FAQ >>

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: [MFC] Cum schimb date cu un dialog modal? (5)

Post by Ovidiu Cucu » 02 Jan 2009, 14:38

O solutie similara cu cea de la cazul 1 care pune ceva mai mult la treaba ClassWizard-ul este ca sa setam un foreign class si un foreign variable pentru clasa dialogului modal.
Foreign class and variable.gif
Foreign class and variable.gif (12.93 KiB) Viewed 6708 times
ClassWizard-ul adauga membrul CMainDialog* m_pMainDialog

Code: Select all

class CTestDialog : public CDialog
{
// ...
// Dialog Data
   //{{AFX_DATA(CTestDialog)
   CMainDialog*   m_pMainDialog;
   //}}AFX_DATA
// ...
};
pe care il va folosi mai departe int funcii DDX pentru a realiza schimbul de date intre dialogul modal si foreign dialog-ul asociat.
Add member variable.gif
Add member variable.gif (8.12 KiB) Viewed 6707 times
Wizard-ul va avea grija sa puna DDX-urile.

Code: Select all

void CTestDialog::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(CTestDialog)
   DDX_Text(pDX, IDC_EDIT1, m_pMainDialog->m_strEdit1);
   // ...
   //}}AFX_DATA_MAP
}
Super, pana aici am facut totul cu wizard-ul!
Totusi ne mai ramane si noua ceva de facut si anume sa includem header-ele necesare si, inainte de apelul functiei DoModal sa initializam CTestDialog::m_pMainDialog cu un pointer la fereastra cu care se face schimbul de date:

Code: Select all

void CMainDialog::ShowModalDialog() 
{
   UpdateData();

   CTestDialog dlg;
   dlg.m_pMainDialog = this; // <-- init the foreign variable.
   if(IDOK == dlg.DoModal())
   {
      UpdateData(FALSE);
   }	
}
Nota: Metoda descrisa mai sus se aplica pentru Visual Studio 6.0. Se poate face si cu Visual Studio 200x, insa va trebui sa punem manual variabilele si functiile DDX corespunzatoare.

Aplicatie demo
DemoModal4.zip
(11.35 KiB) Downloaded 359 times

Next FAQ >>

Post Reply