Sunteți pe pagina 1din 16

Capitolul 8.

Casete de dialog modale i nemodale

159

8. Casete de dialog modale i nemodale n marea majoritate a aplicaiilor, o aciune asupra butoanelor implic apariia unor noi casete de dialog, numite casete copil, care la rndul lor vor conine o serie de obiecte de interfa. Aceste casete, pot lansa apoi alte casete i aa mai departe. Pentru fiecare caset suplimentar inserat n proiect, trebuie adugat o resurs de tip caset de dialog i o clas asociat, derivat din clasa CDialog. Caseta de dialog poate fi creat prin intermediul editorului de resurse (ClassView), iar clasa derivat este creat de ctre ClassWizard. Noua clas poate fi dezvoltat pentru a asigura gestiunea controalelor aflate n spaiul noii machete. Marea majoritate a casetelor de dialog copil sunt lansate n execuie ca i casete de dialog modale. La afiarea unei astfel de casete, restul interfeei cu utilizatorul devine inaccesibil, fiind necesar nchiderea casetei nainte de a relua lucrul obinuit. Casetele de dialog pot fi cascadate, astfel nct o caset s o apeleze pe alta, determinnd n acest fel transmiterea controlului ctre caseta de dialog cel mai recent deschis, care este de altfel i caseta activ. Dup nchiderea acesteia, se red controlul casetei de dialog apelante. Exist i posibilitatea lansrii casetelor de dialog ca i casete de dialog nemodale, ca alternativ la casetele de dialog modale. Aceste casete permit accesul i la restul interfeei n timpul n care sunt afiate. n fapt, o serie de controale sunt implementate ca i casete de dialog nemodale. 8.1 Cum adugm o nou caset de dialog Pentru a vedea cum se pota duga nou casete de dialog proiectului, s creem un nou proiect MFC AppWizard(.exe) de tip Dialog Based, numit wiz5. Caseta de dialog asociat acestui proiect o vom configura ca n fig. 8.1.

Figura 8.1.Caseta de dialog printe

Am adugat casetei de dialog casetele de editare (de jos n sus) IDC_NUME i IDC_PRENUME i butoanele cu identificatorii IDC_COPII i IDC_AFISEAZA, avnd respectiv etichetele Copii i Afiseaza. De asemenea, am mapat casetelor de editare, respectiv variabilele de categorie Value, CString m_strNume i CString m_strPrenume. S implementm funcia ce rspunde apsrii butonului IDC_AFISEAZA:

H. Vlean 2003

160

Programarea Interfeelor Windows n Visual C++

void CWiz5Dlg::OnAfiseaz() { // TODO: Add your control notification handler code here UpdateData(); CString strText; strText += m_strNume+" " + m_strPrenume; MessageBox(strText,"Nume parinte",MB_ICONEXCLAMATION); }

Va trebui acum s adugm o nou caset de dialog proiectului nostru. Pentru aceasta, vom efectua urmtoarele: vom selecta ResourceView pentru a afia resursele proiectului; apsm click stnga pe wiz5 resources i apoi se apsm click dreapta pentru a afia meniul contextual; alegem opiunea Insert..., pentru a deschide caseta de dialog Insert Resource (fig. 8.2).

Figura 8.2. Inserm o nou caset de dialog

selectm Dialog pentru pentru a arta c dorim inserarea unei casete de dialog ; apsm butonul New i noua resurs de tip caset de dialog este adugat proiectului. Ea trebuie s apar acum n ResourceView ca subordonat (de tip Dialog) lui wiz13 resources. Ea are implicit identificatorul IDD_DIALOG1. Dorim s schimbm acest identificator; apsm click dreapta pentru afiarea meniului contextual i alegem Properties; modificm identificatorul noii casete n IDD_CASETAMODALA; Urmeaz s adugm controale casetei de dialog. Vom aduga controale ca i n fig. 8.3:

Figura 8.3 Caseta de dialog modal

Am adugat casetei, 3 casete de editare i o caset combinat. Prima caset de editare are identificatorul IDC_NUME i stilul Read_only validat. A doua caset de

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale

161

editare are identificatorul IDC_PRENUME, iar a treia IDC_VIRSTA. Caseta combinat are identificatorul IDC_SEX i stilul Sort nevalidat. Dorim acum s mapm controale variabilelor. Dac lansm ClassWizard, aceasta va sesiza existena unei noi resurse de tip dialog i va lansa un dialog prin intermediul cruia va asocia o clas (implicit derivat public din CDialog) noii resurse. Pentru aceasta: apsm click pe noua resurs pentru a o selecta (sau dublu click pe IDD_CASETAMODALA din ResourceView); lansm ClassWizard; ClassWizard va sesiza c am inserat o nou caset de dialog va afia caseta Adding a Class (fig. 8.4);

Figura 8.4. Adugm o nou clas

apsm OK pentru acceptarea opiunii implicite de creare a unei noi clase; ClassWizard va afia caseta de dialog New Class (fig. 8.5);

Figura 8.5. Aa definim noua clas

n caseta de editare Name: vom specifica numele noii clase, care ncapsuleaz noua caset de dialog. Prin convenie, clasele derivate din clasele MFC ncep cu litera C. Fie CModalaDlg numele acestei noi clase; o dat cu tastarea numelui pentru clasa derivat, automat, n caseta de editare File Name: va apare numele fiierului care conine definiia clasei. Acest nume este implicit similar cu numele clasei, deci, n cazul nostru, ModalaDlg.cpp. Acest nume poate fi schimbat de ctre utilizator; n caseta combinat Base class: este afiat superclasa din care este derivat noua clas. Aceasta, pentru o clas care implementeaz o caset de dialog, este implicit CDialog; n caseta combinat Dialog ID: este afiat identificatorul noii casete de dialog. i acesta poate fi schimbat, n acest caz clasa referindu-se la alt caset; apsm OK pentru crearea noii clase, scheletul acesteia fiind creat automat n fiierele .h i .cpp;

H. Vlean 2003

162

Programarea Interfeelor Windows n Visual C++

ClassWizard va afia apoi pagina Message Maps obinuit, putnd fi adugate funcii de tratare a evenimentelor din noua caset de dialog; Noi vom trece n pagina Member Variables i vom asocia controalelor urmtoarele variabile (dup ce ne convingem c n caseta combinat Class name: este trecut CModalaDlg): casetelor de editare IDC_PRENUME i IDC_VIRSTA variabilele de categorie Value CString m_strPrenume i int m_nVirsta i casetei combinate IDC_SEX variabila de categorie Control, CComboBox m_cbSex. apsm OK pentru nchiderea Class Wizard;

Dup adugarea noii clase care ncapsuleaz caseta de dialog, aceasta va apare n Class View. Toate funciile asociate evenimentelor generate de controalele de pe caseta de dialog copil i respectiv toate variabilele mapate acestora, vor aparine acestei clase. Implicit, pentru aceast clas sunt create constructorul i o funcie de schimb de date. Constructorul clasei are implementarea:
CModalaDlg::CModalaDlg(CWnd* pParent /*=NULL*/) : CDialog(CModalaDlg::IDD, pParent) { //{{AFX_DATA_INIT(CModalaDlg) //}}AFX_DATA_INIT }

Constructorul primete ca parametru un pointer spre clasa CWnd i construiete noua caset apelnd constructorul clasei de baz, CDialog, cruia i transmite un identificator, definit n clasa derivat, CModalaDlg::IDD. Acest identificator este declarat n definiia clasei (fiierul ModalaDlg.h) ca o valoare de tip enumerare i iniializat cu identificatorul de resurs al casetei de dialog:
class CModalaDlg : public CDialog { // Construction public: CModalaDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CModalaDlg) enum { IDD = IDD_CASETAMODALA };

... Observaie: n cazul n care proiectul conine mai multe clase de dialog, este necesar acordarea unei atenii sporite n cazul inserrii de noi variabile cu ajutorul ClassWizard. Trebue ca s fie selectat clasa potrivit n caseta Class name:, n caz contrar variabila fiind asociat unei alte clase. Noua caset de dialog este apelat ca rspuns la o aciune asupra unui buton din caseta printe, sau la selectarea unui meniu. Deci, n funcia de rspuns la evenimentul din caseta printe, va trebui realizat i afiarea casetei de dialog derivate. Vom realiza aceasta n doi pai: vom crea o instan a clasei dialog, declarnd un obiect local funciei de tratare, ca de exemplu
CModalaDlg dlgCasetaModala(this);

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale

163

Ce facem de fapt? Am declarat un obiect de clasa CModalaDlg. S ne uitm cteva rnduri mai sus i s vedem c trebuie s-i transmitem constructorului un pointer CWnd, mai clar spus, un pointer spre caseta de dialog printe. Cum n acest moment aceasta este caseta activ, vom transmite constructorului pointerul this (v reamintii?). Pentru ca procesul de compilare s recunoasc clasa de dialog derivat, trebuie ca fiierul header de definiie a clasei s fie inclus la nceputul modulului care conine instanierea. De exemplu, n cazul nostru, dac definiia noii clase se afl n ModalaDlg.h, n fiierul wiz5Dlg.cpp care instaniaz aceast clas, la nceput va fi inserat linia
/ wiz5Dlg.cpp : implementation file // #include #include #include #include "stdafx.h" "wiz5.h" "wiz5Dlg.h" "ModalaDlg.h"

#ifdef _DEBUG

vom afia caseta i vom declana operarea modal a casetei de dialog. Acest lucru se face prin apelul funciei DoModal(). Ca efect, va fi afiat caseta de dialog mpreun cu controalele sale componente i va fi iterat o bucl de mesaje locale, mesajele fiind transmise de la controalele casetei de dialog ctre implementarea utilizator, pn la nchiderea casetei de dialog.

Dac apare o eroare la afiarea noii casete de dialog, DoModal() returneaz un cod de eroare, care poate fi 1 sau IDABORT. Dac afiarea casetei reuete, din DoModal() se revine la apelul implicit al funciei EndDialog(). Funcia EndDialog() primete ca parametru o valoare ntreag, care specific cine a apelat-o, valoare ntoars apoi funciei DoModal() care o utilizeaz ca i cod de retur. Funcia EndDialog() este apelat n implementrile implicite ale funciilor OnOK() i OnCancel(), lansate n execuie la producerea evenimentului BN_CLICKED pentru butoanele OK i Cancel. n acest caz, ea va transmite respectiv codurile IDOK i respectiv IDCANCEL funciei DoModal(). Caseta de dialog poate fi nchis de utilizator n orice moment i prin program, prin apelarea funciei EndDialog(). Implementarea funciei OnCopii() este:
void CWiz5Dlg::OnCopii() { // TODO: Add your control notification handler code here CModalaDlg dlgCasetaModala(this); int nRetCode; UpdateData(); if (!m_strNume.IsEmpty()) { dlgCasetaModala.m_strNume=m_strNume;

H. Vlean 2003

164

Programarea Interfeelor Windows n Visual C++ nRetCode = dlgCasetaModala.DoModal(); } else MessageBox("Nu avem un nume de parinte!", "Eroare",MB_ICONSTOP);

Am definit caseta derivat care va fi identificat prin dlgCasetaModala, fiind apoi afiat. Se pot face unele iniializri a anumitor variabile, presupunnd de exemplu c numele copilului este acelai cu al printelui i c acesta a fost completat. Iniializarea se face nainte de afiarea casetei derivate. Iniializrile mai complexe, cum ar fi popularea casetei combinate, se fac altfel. Se poate observa c la compilare, o linie de forma
dlgCasetaDerivata.m_cbSex.AddString(F);

nu va da eroare de compilare, dar va da eroare de aserie la execuie. S ne reamintim c astfel de iniializri se fceau, pentru fereastra printe, n funcia OnInitDialog(). Dac ne uitm n implementarea casetei derivate, constatm c aceast funcie nu exist. Dar, ea poate fi implementa, ca funcia ce rspunde mesajului WM_INITDIALOG pentru caseta de dialog modal. Deci, o vom putea crea cu ajutorul Class Wizard, avnd grij ca la Class name: s fie selectat CModalaDlg. i la Object IDs: CWiz5Dlg. Implementarea funciei de iniializare va fi:
BOOL CModalaDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here PopulezCombo(); return TRUE; // return TRUE unless ...

Vom aduga clasei CModalaDlg funcia membri void PopulezCombo() pentru iniializarea valorilor casetei combinate IDC_SEX: Vom aduga tot clasei CModalaDlg variabila membru int nIndex.
void CModalaDlg::PopulezCombo() { m_cbSex.AddString("F"); m_cbSex.AddString("M"); int nIndex=m_cbSex.SelectString(-1,"F"); }

Va trebui acum s prelum textul selectat n caseta combinat. Pentru aceasta, vom aduga clasei CModalaDlg variabila membru CString m_strSex i vom implementa funcia ce rspunde mesajului CBN_SELCHANGE:
void CModalaDlg::OnSelchangeSex() { // TODO: Add your control notification handler code here int nIndex=m_cbSex.GetCurSel(); if (nIndex != CB_ERR) m_cbSex.GetLBText(nIndex, m_strSex); }

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale

165

Va trebui s modificm funcia PopulezCombo(), astfel nct irul m_strSex s fie actualizat i pentru valoarea implicit din caseta combinat:
void CModalaDlg::OnSelchangeSex() { ... if (nIndex != CB_ERR) m_cbSex.GetLBText(nIndex, m_strSex); }

8.2 Schimbul i validarea datelor Atunci cnd se mapeaz variabile controalelor, ClassWizard genereaz automat cod pentru efectuarea schimbului de date ntre controale i variabilele asociate, prin intermediul funciei DoDataExchange() a clasei de dialog. Orice clas de dialog va conine o astfel de funcie. Ea rspunde de transferul datelor n ambele direcii, nspre i dinspre controalele casetei de dialog. Procesul de transfer se numete schimb de date i este iniiat printr-un apel al funciei UpdateData(). Aceast funcie are ca parametru variabila BOOL bSaveAndValidate, care ia implicit valoarea TRUE. S ne reamintim c valoarea acestei variabile dicteaz sensul transferului informaiei: dac este FALSE, controalele sunt actualizate pe baza coninutului variabilelor asociate, iar dac este TRUE, variabilele iau valoarea nscris n controale. Funcia UpdateData() este apelat automat de funcia OnInitDialog(), cu parametrul FALSE pentru a iniializa controalele la afiarea casetei de dialog. Funcia OnOK()apeleaz implicit UpdateData() cu parametrul TRUE pentru actualizarea variabilelor asociate cu coninutul controalelor. Exist dou tipuri de funcii pentru efectuarea transferului de date. 8.2.1 Funcii pentru schimbul de date DDX (Do Data Exchange) Funcia DoDataExchange() pentru caseta modal din exemplul wiz5 este:
void CModalaDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CModalaDlg) DDX_Control(pDX, IDC_SEX, m_cbSex); DDX_Text(pDX, IDC_NUME, m_strNume); DDX_Text(pDX, IDC_PRENUME, m_strPrenume); DDX_Text(pDX, IDC_VIRSTA, m_nVirsta); //}}AFX_DATA_MAP } Funcia primete ca parametru un pointer la clasa CDataExchange. Aceast clas

implementeaz toate detaliile referitoare la transferul de date, inclusiv referitor la fereastra vizat de transfer. De altfel i variabila bSaveAdnValidate este o variabil membru a acestei clase. Pointerul pDX este apoi utilizat n apelul funciilor DDX_Text sau DDX_Control (generate de ClassWizard, n funcie de tipul variabilei care mapeaz controlul) n scopul implementrii transferului de date. Exist mai multe

H. Vlean 2003

166

Programarea Interfeelor Windows n Visual C++

astfel de funcii DDX_, care rspund la diferite tipuri de controale i de date. Funciile DDX_ specific modul efectiv de executare a transferului. De exemplu, linia DDX_Text(pDX, IDC_NUME, m_strNume); se citete astfel: se execut transferul de date de tip ir de caractere, n sensul indicat de pDX, ntre controlul IDC_NUME i variabila m_strNume. Exist mai multe categorii de funcii DDX_ de transfer de date de tip valoare, prezentate n tabelul 8.1
Tabelul 8.1 Nume DDX_Text Tipuri de control Caset de editare Tipuri de date asociate BYTE, short, int, UINT, long, DWORD, CString, LPTSTR, float, double, COleCurrency, COleDateTime int int CString CString CString CString int int int

DDX_Check DDX_Radio DDX_LBString DDX_LBStringExact DDX_CBString DDX_CBStringExact DDX_LB_Index DDX_CBIndex DDX_Scroll

Caset de validare Grup de butoane de opiune Caset cu list derulant Caset cu list derulant Caset combinat Caset combinat Caset cu list derulant, val. de index Caset combinat, val. de index Bar de derulare

Implicit, aceste funcii sunt adugate automat de ClassWizard, dar pot fi adugate i de utilizator, manual, n funcia DoDataExchange(), respectnd aceleai reguli cu ClassWizard. Dac dorim s transferm valori ntre controale i variabilele asociate, va trebui s utilizm funcia UpdateData(). Dar, am observat c, n exemplul cu caseta modal, numele s-a transmis acesteia fr s fie nevoie s apelm UpdateData(). Este normal, s ne reamintim c OnInitDialog() apeleaz implicit aceast funcie, cu parametrul FALSE. De asemenea, s ne reamintim c OnOK() apeleaz aceeai funcie, cu parametrul TRUE. Deci, dac vom utiliza valorile nscrise n controalele casetei de dialog copil doar n caseta de dialog printe, transferul de date n variabile se face implicit, fr s mai apelm UpdateData(). 8.2.2 Funcii pentru validare de date DDV (Do Data Validate) Exist anumite situaii, n care trebuie realizate validri ale variabilelor ce preiau coninutul unei casete de dialog. De exemplu, unei variabile CString mapat peste o caset de editare, i se poate preciza numrul maxim de caractere din irul introdus de utilizator. n cazul unei variabile int de exemplu, se pot preciza limitele superioar i inferioar pentru domeniul n care variabila ia valori. La adugarea unei reguli de validare, ClassWizard va genera linii DDV_ corespunztoare n funcia DoDataExchange(). Dac de exemplu, IDC_VIRSTA poate lua valori doar ntre 0 i 18 (precizate n cmpurile MinimumValue: i Maximum Value: din pagina Member Variables al ClassWizard), aceast funcie va fi
void CModalaDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX);

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale ... DDX_Text(pDX, IDC_VIRSTA, m_nVirsta); DDV_MinMaxInt(pDX, m_nVirsta, 0, 18); //}}AFX_DATA_MAP

167

Se observ apariia liniei DDV_MinMaxInt(pDX, m_nVirsta, 0, 18); care specific faptul c variabila m_nVirsta ia valori ntre 0 i 18. Exist mai multe categorii de funcii DDV_ standard, prezentate n tabelul 8.2:
Tabelul 8.2 Funcia DDV DDV_MaxChars DDV_MinMaxByte DDV_MinMaxDouble DDW_MinMaxDword DDV_MinMaxFloat DDV_MinMaxInt DDV_MinMaxLong DDV_MinMaxUnsigned Tip de date CString BYTE double DWORD float int long unsigned Descriere Limiteaz nr. de caractere introduse Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat Limiteaz nr. la un interval specificat

Se pot crea funcii de validare proprii, care s efectueze verificri utilizator, construind funcii DDV_ personalizate. n acest scop, trebuie creat o funcie care primete un pointer la clasa CDataExchange, o referin la variabila mapat care va fi testat i orice ali parametri specifici. Prima condiie pe care trebuie s o verifice funcia este dac obiectul CDataExchange se afl n modul salveaz-i-valideaz. Acest lucru se ntmpl dac pDX->m_bSaveAndValidate=TRUE. Dac acest indicator nu este TRUE, atunci se efectueaz iniializarea controalelor pe baza variabilelor membru, aa c nu sunt necesare teste de validare i funcia ar trebui s-i ncheie execuia. Dac variabila mapat este valid, funcia se ncheie normal. n caz contrar, se efectueaz o aciune de eroare, care poate afia o caset de avertizare, sau se pot ntreprinde aciuni de corectare a variabilei. n primul caz, dup afiarea casetei de validare, trebuie apelat funcia Fail() a clasei CDataExchange pentru a informa funcia UpdateData() c validarea a euat. Astfel, actualizarea variabilei cu coninutul controlului nu se va mai face. Se propune implementarea unei validri asupra prenumelui, astfel: se face o validare de text, iar dac prenumele conine i alte caractere dect litere, se mpiedic transferul. Se face i o aciune de corectare, n sensul c dac prima liter din prenume este minuscul, ea este transformat n majuscul. Vom implementa astfel n fiierul ModalaDlg.cpp funcia
void CModalaDlg::DDV_ValidPren(CDataExchange *pDX, CString &nPren) { if (pDX->m_bSaveAndValidate) { for (int i=0;i<nPren.GetLength();i++) { if (!isalpha(nPren[i])) { MessageBox(" Nu sunt acceptate caractere nealfabetice", "EOARE",MB_ICONSTOP); pDX->Fail();

H. Vlean 2003

168 break;

Programarea Interfeelor Windows n Visual C++

} if (!isupper(nPren[0])) { TCHAR x=nPren.GetAt(0); x-=32; nPren.SetAt(0,x); }

Va trebui acum s declarm aceast funcie n fiierul ModalaDlg.h. O vom declara imediat sub declaraia funciei DoDataExchange():
... protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support void CModalaDlg::DDV_ValidPren(CDataExchange *pDX, CString &nPren); ...

Acum, vom introduce linia de validare n funcia DoDataExchange(), imediat sub funcia DDX_ care descrie transferul cu variabila implicat:
void CModalaDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CModalaDlg) ... DDX_Text(pDX, IDC_PRENUME, m_strPrenume); DDV_ValidPren(pDX, m_strPrenume); DDX_Text(pDX, IDC_VIRSTA, m_nVirsta); DDV_MinMaxInt(pDX, m_nVirsta, 0, 18); //}}AFX_DATA_MAP }

n final, valorile variabilelor aparinnd casetei derivate, trebuie preluate i utilizate n caseta printe. Vom aduga urmtoarele variabile membru clasei CWiz5Dlg: CString m_strPrenCopil, CString m_strSexCopil i CString m_nVirstaCopil. Acestea vor prelua valorile corespunztoare la revenirea din caseta modal. Dar acest lucru trebuie s se ntmple numai dac n aceasta am apsat butonul OK:
void CWiz5Dlg::OnCopii() { // TODO: Add your control notification handler code here CModalaDlg dlgCasetaModala(this); int nRetCode; UpdateData(); if (!m_strNume.IsEmpty()) { dlgCasetaModala.m_strNume=m_strNume; nRetCode = dlgCasetaModala.DoModal(); } else MessageBox("Nu avem un nume de parinte!", "Eroare",MB_ICONSTOP); if (nRetCode==IDOK)

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale {

169

} }

m_strPrenCopil=dlgCasetaModala.m_strPrenume; m_strSexCopil=dlgCasetaModala.m_strSex; m_nVirstaCopil=dlgCasetaModala.m_nVirsta;

Va trebui acum s modificm i funcia de afiare, ca mai jos:


void CWiz5Dlg::OnAfiseaza() { // TODO: Add your control notification handler code here UpdateData(); CString strText; strText += m_strNume+" " + m_strPrenume; strText += "\n\n Copii: \n"; strText+= "Nume : " + m_strNume+ "\n"; strText+= "Prenume : " + m_strPrenCopil+ "\n"; strText+= "Sex : " + m_strSexCopil+ "\n"; CString temp; temp.Format("%d",m_nVirstaCopil); strText+= "Virsta : " + temp+ "\n"; MessageBox(strText,"Nume parinte",MB_ICONEXCLAMATION); }

8.3 Casete de dialog nemodale O caset de dialog nemodal nu acapareaz interfaa aplicaiei, aa cum o face caseta de dialog modal. Ea este afiat de obicei pentru a completa funcionalitatea tipic a aplicaiei prin transmiterea de mesaje ctre aplicaia apelant. Casetele de dialog nemodale sunt folosite de obicei ca bare de instrumente neflotante, permind utilizatorului s efectueze click pe butoanele casetei de dialog pentru a indica opiuni privind modul de lucru sau a exprima o alegere. O caset de dialog nemodal folosete ca interfa o caset de dialog obinuit, dar pentru acest tip de caset butoanele OK i Cancel nu mai au un rol important. i acest tip de casete sunt ncapsulate de clase derivate din CDialog, modul de generare a clasei asociate fiind identic cu cel prezentat n paragraful 8.1. Diferenele eseniale ntre casetele de dialog modale i cele nemodale se regsesc n modul de creare i afiare i respectiv modul de nchidere. O caset de dialog nemodal este creat (v reamintii de la API?) prin intermediul funciei Create() a clasei CDialog. Aceast funcie poate fi apelat fie n constructorul clasei ce ncapsuleaz caseta de dialog printe, astfel nct s se creeze o instan a casetei nemodale o dat cu instanierea unei casete de dialog printe, fie ntr-o alt funcie, caseta nemodal fiind creat numai la apelul acestei funcii. Funcia Create() primete doi parametri: primul este identificatorul machetei care se creeaz, iar al doilea este un pointer (implicit NULL) ctre caseta de dialog printe. Cum caseta de dialog nemodal nu acapareaz controlul, vom putea folosi acest pointer pentru a comunica cu caseta de dialog printe. Create() returneaz valoarea TRUE n cazul reuitei, respectiv FALSE n cazul eecului. n cazul succesului, cnd Create() returneaz valoarea TRUE, fereastra exist dar nu este vizibil. Pentru a o face vizibil, este nevoie de un apel al funciei ShowWindow() cu parametrul TRUE. Casetele de dialog modale pot fi n general ncapsulate fr probleme n interiorul unei funcii, deoarece ele preiau controlul absolut al programului n momentul n care sunt lansate n execuie. Casetele nemodale, datorit faptului c nu preiau controlul
H. Vlean 2003

170

Programarea Interfeelor Windows n Visual C++

programului, vor trebui s poat fi accesate n orice punct al acestuia. Din aceast cauz, este bine ca s creem dinamic caseta nemodal, prin intermediul operatorului new, reinnd adresa ei ntr-o variabil pointer global sau membr a clasei ce ncapsuleaz caseta de dialog printe. Caseta de dialog nemodal poate fi nchis la nevoie, distrugnd-o prin intermediul operatorului delete. Destructorul clasei de baz, CDialog, se va ocupa apoi de distrugerea casetei de dialog nemodale. S exemplificm aceste lucruri, completnd caseta de dialog de baz ca n fig. 8.6.

Figura 8.6. caseta de dialog printe modificat

Am adugat o caset cu identificatorul IDC_LISTA i cu stilurile Selection: Multiple i respectiv Sort nevalidat i butoanele IDC_TRIMITE, IDC_START i IDC_STOP, cu etichetele respectiv Trimite, Start i Stop. Casetei cu list i asociem variabila de categorie Control, CListBox m_DisplayList. . Adugm apoi o resurs nou de tip Dialog, creia i schimbm identificatorul n IDD_NEMODALA i care conine controalele din fig. 8.7

Figura 8.7. Caseta de dialog nemodal

Caseta de dialog conine butoanele cu identificatorii IDC_SUS i IDC_JOS i etichetele Sus i respectiv Jos i caseta de editare IDC_MESAJ. Apoi, casetei de dialog nou introduse i se asociaz clasa CNemodalaDlg, respectnd paii de la nceputul capitolului. Clasa va fi implementat n fiierele NemodalaDlg.h i NemodalaDlg.cpp. n final vom asocia casetei de editare IDC_MESAJ variabila de categorie Value CString m_Mesaj. Urmtorul pas este s modificm constructorul clasei asociate casetei de dialog nemodale, astfel nct s stabilim operarea nemodal a casetei de dialog i s o afim n cazul n care a putut fi creat. Se va modifica constructorul, ca mai jos:
CNemodalaDlg::CNemodalaDlg(CWnd* pParent /*=NULL*/) : CDialog(CNemodalaDlg::IDD, pParent) { //{{AFX_DATA_INIT(CNemodalaDlg) if (Create(IDD_NEMODALA,pParent)) ShowWindow(TRUE); m_strMesaj = _T(""); //}}AFX_DATA_INIT

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale }

171

Pentru a deschide i nchide caseta de dialog nemodal, vom asocia mesajului BN_CLICKED generat de butoanele IDC_START i IDC_STOP funcii. Va trebui nti s includem definiia clasei CNemodalaDlg n fiierul wiz5Dlg,cpp: Vom declara apoi un pointer global, p_Nemodala, care va menine adresa casetei de dialog nemodale. Dac nu este lansat nici o caset de dialog, acest pointer trebuie s fie NULL.
// wiz5Dlg.cpp : implementation file ... #include "ModalaDlg.h" #include "NemodalaDlg.h" #ifdef _DEBUG ... #endif CNemodalaDlg* p_Nemodala=NULL;

De acum nainte, dac exist lansat o caset nemodal, orice variabil sau funcie din clasa asociat acesteia, va putea fi apelat din orice punct al programului, prin intermediul acestui pointer. Implementrile funciilor de tratare a apsrii butoanelor sunt:
void CWiz5Dlg::OnStart() { // TODO: Add your control notification handler code here if (!p_Nemodala) p_Nemodala = new CNemodalaDlg(this); } void CWiz5Dlg::OnStop() { // TODO: Add your control notification handler code here if (p_Nemodala) { delete p_Nemodala; p_Nemodala=NULL; } } Funcia OnStart() testeaz dac pointerul global are valoarea NULL. Dac da,

nseamn c nu avem o caset de dialog modal deschis i este creat o instan a acesteia. S ne reamintim c n constructor era apelat funcia ShowWindow(TRUE), deci caseta va fi automat afiat. Dac nu testm faptul c p_Nemodala=NULL, putem lansa oricte casete nemodale, dar cum n programul principal exist un singur pointer care menine adresa casetelor nemodale, la fiecare nou lansare, legtura cu caseta nemodal precedent este pierdut. Funcia OnStop() testeaz dac pointerul global are valoarea NULL. Dac nu o are, nseamn c avem o caset deschis i atunci ea poate fi distrus, de nchiderea ei ocupndu-se, dup cum s-a artat anterior, destructorul clasei CDialog. Apoi pointerului global i se atribuie valoarea NULL, pentru a specifica c respectiva caset nu e deschis. Transmiterea de valori din caseta de dialog printe ctre controale sau variabile ale casetei de dialog nemodale, sau apelurile funciilor membru ale unei casete de dialog nemodale se pot efectua pe toat durata de via a acesteia, prin intermediul

H. Vlean 2003

172

Programarea Interfeelor Windows n Visual C++

pointerului de acces corespunztor. La transferul de date ntre controale i variabilele membru mapate, prin UpdateData() i DoDataExchange() se aplic regulile deja cunoscute. Este posibil de asemenea s fie apelate funcii sau s fie fixate variabile membru ale altor obiecte ale aplicaiei n cadrul casetei de dialog modale, ca rspuns la interaciunea utilizatorului cu controalele. n acest scop, trebuie transmii casetei de dialog pointeri la obiectele respective ale aplicaiei. Constructorul casetei de dialog nemodale este un loc bun pentru transmiterea acestor pointeri. Pentru aceasta, se modific constructorul clasei asociate, astfel nct la crearea casetei de dialog s fie transmii pointeri la obiectele respective ale aplicaiei. Apoi aceti pointeri pot fi reinui de ctre caseta de dialog sub form de variabile membru, astfel nct oricare dintre funciile componente va putea accesa obiectele n cauz. Ca exemplu, vom scrie programul astfel nct la apsarea butoanelor IDC_SUS i IDC_JOS a casetei nemodale, s fie transmise i afiate mesajele ** SUS** i respectiv ** JOS** n caseta cu list. De asemenea, n sens invers, la apsarea butonului IDC_TRIMITE a casetei de baz, n caseta de editare IDC_MESAJ a casetei nemodale va fi afiat mesajul Am primit mesajul **SUS** sau Am primit mesajul **JOS** n funcie de ultimul mesaj primit, sau Nu am primit nimic dac nu a fost transmis nici un mesaj. Prima dat, va trebui s modificm constructorul clasei casetei de dialog nemodale, pentru a ne asigura c pointerul transmis ctre printe este CWiz5Dlg* i nu pointerul CWnd* curent. Pentru aceasta, se ajustez ca mai jos definiia constructorului din fiierul antet NemodalaDlg.h:
class CNemodalaDlg : public CDialog { // Construction public: CNemodalaDlg(CWiz5Dlg* pParent = NULL);

De asemenea, la nceputul fiierului cu definiia clasei CNemodalaDlg, adic NemodalaDlg.cpp, va trebui s inserm fiierul de definie al clasei CWiz5Dlg. Acest fiier trebuie inclus naintea includerii fiierului NemodalaDlg.h, pentru c n acesta apare o referire la clasa CWiz5Dlg.
// NemodalaDlg.cpp : implementation file #include "stdafx.h" #include "wiz5.h" #include "wiz5Dlg.h" #include "NemodalaDlg.h"

Pointerul pParent spre caseta de dialog printe exist doar n constructorul clasei casetei de dialog nemodale. Noi va trebui s-l memorm, astfel nct s-l putem utiliza n diferite funcii ale clasei. Pentru aceea, vom aduga clasei CNemodalaDlg variabila membru protejat CWiz5Dlg* m_pParent. Acesta va fi pointerul n care se va copia pParent. Vom modifica constructorul clasei care ncapsuleaz caseta de dialog nemodal, ca mai jos:
CNemodalaDlg::CNemodalaDlg(CWiz5Dlg* pParent /*=NULL*/) : CDialog(CNemodalaDlg::IDD, pParent)

H. Vlean 2003

Capitolul 8. Casete de dialog modale i nemodale {

173

//{{AFX_DATA_INIT(CNemodalaDlg) if (Create(IDD_NEMODALA,pParent)) { ShowWindow(TRUE); m_pParent=pParent; } m_strMesaj = _T(""); //}}AFX_DATA_INIT

Acum, putem asocia funciile pentru apsarea butoanelor din caseta de dialog nemodal, accesnd elemente din caseta de dialog printe prin intermediul pointerului m_pParent:
void CNemodalaDlg::OnSus() { // TODO: Add your control notification handler code here m_pParent->m_DisplayList.AddString("** SUS **"); } void CNemodalaDlg::OnJos() { // TODO: Add your control notification handler code here m_pParent->m_DisplayList.AddString("** JOS **"); }

De asemenea, putem implementa funcia ce rspunde la evenimentul BN_CLICKED corespunztor butonului IDC_TRIMITE din caseta de dialog printe.
void CWiz5Dlg::OnTrimite() { // TODO: Add your control notification handler code here int nr; CString Mesaj; nr=m_DisplayList.GetCount(); if (p_Nemodala) { if (nr) { m_DisplayList.GetText(nr-1, Mesaj); p_Nemodala->m_strMesaj = "Am primit mesajul " + Mesaj; } else p_Nemodala->m_strMesaj = CString("Nu am primit nimic!"); p_Nemodala->UpdateData(FALSE); } }

Se observ ca accesul la caseta de editare din caseta de dialog nemodal este fcut tot pe baza pointerului la caseta de dialog respectiv. n acest moment, programul este funcional, dar are o mic problem. Cu toate c din macheta casetei nemodale au fost nlturate butoanele OK i Cancel, aceasta poate fi nchis forat prin click pe pictograma cruciuli din dreapta sus. Prin nchiderea forat a casetei, dac fereastra principal nu intercepteaz un mesaj de nchidere, pointerul i memoria asociate pentru caseta nemodal rmn alocate, cu toate c obiectul nu mai exist. Aceasta poate duce la pierdere de memorie i n plus, nu va putea fi apelat o nou instan, pn cnd vechea instan nu va fi nchis corect.
H. Vlean 2003

174

Programarea Interfeelor Windows n Visual C++

Rezolvarea acestei probleme se poate face n dou moduri: interceptarea mesajului WM_CLOSE pentru clasa CNemodalaDlg. S ne reamintim cum: n ClassWizard, la pagina Messsage Maps, n caseta combinat Class name: se alege CNemodalaDlg, n caseta Object IDs: tot CNemodalaDlg, iar n caseta cu list Messages: se alege WM_CLOSE. Se apas apoi butoanele Add Function i Edit Code.

extern CNemodalaDlg* p_Nemodala; void CNemodalaDlg::OnClose() { // TODO: Add your message handler code here and/or call default CDialog::OnClose(); delete(this); p_Nemodala=NULL; }

Definirea poinetrului global extern p_Nemodala ne asigur faptul c acesta este acelai pointer cu cel definit n clasa CWiz5Dlg. n funcie, dup distrugerea casetei de dialog nemodale, acest pointer este fcut NULL. nlturarea butonului cruciuli, prin deselectarea stilului System menu pentru caseta nemodal.

ntrebri i probleme propuse 1. Implementai i executai toate exemplele propuse n capitolul 8; 2. Adugai proiectului o nou caset de dialog, pe care s o lansai nemodal din caseta de dialog modal. Ce se ntmpl dac nchidem caseta de dialog modal n timp ce caseta de dialog nemodal copil este deschis? Explicai. 3. Adugai mai multe casete de dialog proiectului, pe care s le lansai nemodal n cascad. Ce se ntmpl dac se nchid casete de la baza lanului? Explicai.

H. Vlean 2003