Programare Orientata Pe Obiecte

185
UNIVERSITATEA DIN BACĂU FACULTATEA DE INGINERIE Culea George Găbureanu Cătălin PROGRAMAREA ORIENTATĂ PE OBIECTE Note de curs – Îndrumar de laborator Editura Alma Mater Bacău 2007

Transcript of Programare Orientata Pe Obiecte

Page 1: Programare Orientata Pe Obiecte

UNIVERSITATEA DIN BACĂU FACULTATEA DE INGINERIE

Culea George Găbureanu Cătălin

PROGRAMAREA ORIENTATĂ PE OBIECTE Note de curs – Îndrumar de laborator

Editura Alma Mater Bacău 2007

Page 2: Programare Orientata Pe Obiecte
Page 3: Programare Orientata Pe Obiecte

3

Page 4: Programare Orientata Pe Obiecte

3

1 CONCEPTE GENERALE

1.1 Introducere Stilul de programare care îl numim în mod obişnuit programare orientată pe

obiecte POO a apărut relativ recent în istoria limbajelor de programare. Acesta este un stil particular şi foarte comod pentru numeroase situaţii. El a fost creat pentru a depăşii limitele programării structurate bazate în principal pe utilizarea largă a procedurilor, funcţiilor, a pointerilor sau a tipurilor de date mai mult sau mai puţin evoluate. Programarea structurată foarte practică mai ales pentru sisteme de programe mici sau aplicaţii reduse negrafice, este depăşită în ceea ce priveşte aplicaţiile mari cu elemente grafice unde mult mai indicată este programarea orientată pe obiecte. Programarea orientată pe obiecte utilizează elementele programării dar se bazează pe orientarea pe obiecte. Orientarea pe obiecte înseamnă organizarea de resurse soft ca şi o colecţie de obiecte, distincte şi discrete, care înglobează atât structuri de date cât şi funcţiile de prelucrare ale acestora. Această organizare este o extindere a programării structurate, în care structurile de date şi funcţiile de prelucrare sunt doar lejer conectate. Toate obiectele au identitate proprie şi sunt perfect distinctibile.

Un obiect se defineşte ca fiind un concept, o abstractizare, un element precis şi util pentru aplicaţia de rezolvat. Obiectele servesc două scopuri precise:

- oferă o mai bună înţelegere a problemei de rezolvat; - oferă un "schelet" de pornire pentru implementare.

O clasă de obiecte grupează un număr oarecare de obiecte cu proprietăţi similare. Această similitudine se referă atât la descriere (date sau atribute), cât şi la comportare (funcţii sau metode), dar în acelaşi timp şi la relaţiile posibile cu alte obiecte. Diferenţele dintre obiectele de aceeaşi clasă se materializează în diferenţe între valorile datelor de descriere. Totodată, pentru fiecare obiect este specificat tipul clasei din care provine. Clasa este elementul de programare care ne permite transcrierea cât mai bună a unui concept din viaţa concretă într-un limbaj de programare. Ea permite definirea atât a datelor relativ la o entitate cât şi a acţiunilor asociate (funcţii sau metode).

Implementare software a unui concept cum ar fi un sistem de calcul se poate printr-o clasă. Această clasă va fi caracterizată de anumite date (atribute) şi anumite acţiuni (metode, funcţii). Această clasă va conţine numai elementele caracteristice, putând definii pe baza ei şi concepte particulare cum ar fi laptop şi PDA. Reprezentarea acestor clase este dată în figura 1.1.

Page 5: Programare Orientata Pe Obiecte

4

Atributele reprezintă caracteristici unice în cadrul unei clase de obiecte. La nivel de obiect fiecare atribut primeşte o anumită valoare care se poate modifica pe parcursul duratei de viaţă a obiectului respectiv. Două sau mai multe obiecte pot avea valori identice sau valori diferite pentru un acelaşi atribut.

Figura 1.1 Exemplu de reprezentare a coceptelor prin clase Metodele sau operaţiile sunt funcţii de prelucrare care se aplică obiectelor de o

anumită clasă. Toate obiectele unei clase admit acelaşi set de metode, metode care la rândul lor pot să primească un număr oarecare de parametri suplimentari. Identificarea unei metode se face atât prin nume, cât şi prin semnătură.

Pentru ca unui obiect să i se poată aplica metode - funcţii de prelucrare - acel obiect trebuie să fie creat (definit). Definirea unui obiect poartă numele de instanţiere. După ce un obiect şi-a îndeplinit misiunea el este eliminat. Durata de viaţă a obiectelor depinde de la obiect la obiect şi de la aplicaţie la aplicaţie.

Abstractizarea este o însuşire umană fundamentală care ne permite să construim modele şi astfel să facem faţă complexităţii. În orice domeniu de activitate umană,

Sistem de calcul

date: - tip memoriei - tip unitate centrală - echipament de

intrare - sistem de operare

acţiuni:

- instalare programe - pornire/oprire

Laptop

date: - monitor rotativ - DVD WR - camera web

acţiuni: - inchidere/

deschidere laptop - conectare monitor

extern

PDA

date: - dimensiuni - tuch-screen - programe

preinstalate acţiuni:

- lansare GPS

Page 6: Programare Orientata Pe Obiecte

5

abordarea unui proiect porneşte de la construirea unui model cu scopul unei mai bune înţelegeri a problemei de rezolvat. Ingineria soft nu este o excepţie.

Prin abstractizare se izolează aspectele esenţiale de cele neesenţiale, bineînţeles în funcţie de perspectiva de abordare a problemei de rezolvat. în consecinţă, pentru o aceeaşi problemă, pot exista mai multe abstractizări, deci mai multe modele. Nu putem vorbi de modele corecte şi modele incorecte, ci de modele adecvate şi modele inadecvate. Prin esenţă, o abstractizare este o reprezentare incompletă a realităţii şi tocmai aceasta dă valoare modelului corespunzător.

La fel ca şi în abordarea structurată, în abordarea orientată pe obiecte, procesul dezvoltării unei resurse soft, porneşte de la un model. Aceasta este o abstractizare menită a face posibilă etapa următoare de implementare. în domeniul ingineriei soft, programarea structurată a făcut un mare pas înainte introducând necesitatea abordării unei aplicaţii din trei perspective diferite, rezultând astfel trei modele diferite. în abordare structurată, cele trei modele, pe care le numim şi sub-modele sunt: modelul static, modelul dinamic şi modelul funcţional.

În modelarea orientată pe obiecte apar două modificări esenţiale: modelul care captează aspectele statice ale aplicaţiei devine modelul obiectual, iar accentul cade pe acest model şi nu pe cel funcţional. în multe aplicaţii mai simple modelele dinamic şi funcţional se construiesc în primul rând pentru o mai bună înţelegere a modelului obiectual.

Modelul obiectual pune în evidenţă aspectele statice ale aplicaţiei, dar nu prin identificarea unor sub-ansamble, mai mult sau mai puţin arbitrare, ci prin identificarea conceptelor cu care operează aplicaţia. Acestor concepte li se asociază obiecte, iar modelul obiectual conţine o sinteză a acestor obiecte, a relaţiilor dintre ele, precum şi a proprietăţilor lor, atât descriptive cât şi comportamentale. Construirea unui sistem în jurul obiectelor cu care operează, surprinde mult mai bine realitatea decât o abordare care porneşte de la aspecte funcţionale.

Modelul dinamic sugerează succesiunea în timp a creării obiectelor, pe măsura necesităţii existenţei lor, precum şi distrugerea acestora după ce şi-au îndeplinit misi-unea. Crearea unui obiect mai poartă şi numele de instanţiere. Pentru obiectele implementate la nivelul unui nucleu, deciziile de instanţiere şi distrugere se iau la nivelul clientului acelui nucleu. Totodată, modelul dinamic indică ordinea de invocare a aspectelor comportamentale, adică a metodelor, pentru fiecare obiect instanţiat.

Modelul funcţional descrie aspectele comportamentale ale obiectelor, indiferent de ordinea de instanţiere a obiectelor şi indiferent de ordinea de invocare a acestor aspecte. Fiecărui aspect comportamental îi corespunde o funcţie, numită şi metodă. Modelul funcţional, descrie funcţionarea în mare a acestor metode, rezumându-se la ilustrarea relaţiilor între parametrii de intrare şi valorile de ieşire ale fiecărei metode în parte, fără însă a oferi detalii legate de implementarea algoritmilor corespunzători.

Există la ora actuală mai multe metodologii orientate pe obiecte pentru analiza, proiectarea şi implementarea de resurse soft. Una dintre metodologii de modelare este metodologia OMT - Object Modeling Technique. Aceasta metodă de modelare presupune o etapizare a etapelor de dezvoltare, precum şi un sistem grafic de reprezentare a obiectelor şi a relaţiilor dintre acestea. Metodologia se bazează pe construirea unui model tridimensional al aplicaţiei, pe baza căruia se adaugă gradat detaliile de implementare în ultima fază a procesului de dezvoltare.

Elementele caracteristice modelării OMT sunt prezentate în figura 1.2.

Page 7: Programare Orientata Pe Obiecte

6

Dezvoltarea unei aplicaţii în metodologia OMT presupune parcurgerea în mod repetitiv, până la obţinerea unui produs acceptabil, a următoarelor etape:

1. Analiza - Această etapă porneşte de la specificaţiile problemei de rezolvat. Scopul acestei etape constă într-o înţelegere profundă a domeniului aplicaţiei, precum şi a cerinţelor concrete ale aplicaţiei. O analiză corectă captează aspectele esenţiale, evitându-se, prin orice mijloace, pierderea generalităţii prin introducerea de aspecte nesemnificative legate de implementare. Rezultatul etapei de analiză este modelul aplicaţiei format din cele trei sub-modele.

Figura 1.2 Caracteristice modelării OMT Un model corect trebuie să poată fi uşor înţeles de specialiştii din domeniul

aplicaţiei, fără cunoştinţe de programare. Excepţie face situaţia în care domeniul aplicaţiei este însăşi ştiinţa şi ingineria calculatoarelor.

2. Proiectarea sistem - În această etapă se iau deciziile de nivel înalt privind arhitectura aplicaţiei, în ansamblul ei. Acum se decid subansamblele mari ale aplicaţiei, precum şi resursele platformei gazdă ce trebuie disponibilizate şi gestionate.

În ce priveşte cerinţele aplicaţiei, acum se aleg modalităţile lor de implementare - hard sau soft -, protocoalele de comunicaţie între nivelele logice adiacente şi se stabilesc legăturile între obiectele ce interacţionează de pe nivele diferite. În aplicaţii mari, se recomandă ca fiecărui nivel să i se construiască un model propriu. În ce priveşte resursele maşinii, acum se stabilesc strategiile de alocare şi gestiune a memoriei dinamice şi a altor resurse sistem.

3. Proiectarea obiectelor - Pornind de la modelul obiectual şi de la deciziile proiectării sistem, în această etapă se adaugă o descriere mai amănunţită a claselor de obiecte. Astfel, acum se specifică numele atributelor şi tipurile lor, precum şi numele metodelor şi semnăturile acestora. Toate aceste nume trebuie alese cu grijă pentru a se mapa cât mai bine domeniului aplicaţiei.

4. Implementarea - În aceasta etapă se trece la dezvoltarea claselor de obiecte şi a relaţiilor dintre acestea. Această implementare se materializează în module soft, sau dacă este cazul, chiar şi în module hard. Partea cea mai semnificativă de efort este dirijată spre codificarea algoritmilor corespunzători metodelor. Această etapă se doreşte a fi simplă şi oarecum mecanică, deoarece toate deciziile majore au fost deja luate în etapele precedente. Tot în această etapă se poate lua decizia introducerii unor obiecte nerelevante din punct de vedere al aplicaţiei, dar indispensabile etapei de implementare, cum ar fi: liste înlănţuite, arbori, tablouri hashing şi altele.

Sigur, întregul proces prezentat trebuie privit ca şi un ciclu interativ. Totodată, efortul necesar testării şi validării, pas cu pas, a produsului nu trebuie deloc neglijat,

Model obiectual

Model dinamic Model funcţional

modelare implementare

Dom

eniu

l pro

blem

ei

Dom

eniu

l sol

uţie

i

Page 8: Programare Orientata Pe Obiecte

7

chiar dacă abordarea orientată pe obiecte duce, printre altele, şi la minimizarea acestei etape.

În fine, fiecare metodologie de proiectare orientată pe obiecte, sugerează un set de reprezentări grafice, folosite pentru reprezentarea modelelor obiectuale. Acestea trebuie să ofere o imagine clară şi intuitivă a claselor şi obiectelor, precum şi a relaţiilor dintre clase, stabilite în faza de modelare.

1.2 Caracteristicile unui limbaj orientat pe obiect Termenii generali ce descriu cel mai bine caracteristicile esenţiale ale unui

limbaj orientat pe obiect sunt: - Abstracţia – defineşte caracteristicile esenţiale unui obiect văzut din exterior.

Selectarea obiectelor abstracte şi a claselor este un punct important în realizarea unui program.

- Încapsularea - este un mecanism care leagă împreună cod şi date şi le păstrează pe ambele în siguranţă faţă de intervenţii din afară şi de utilizări greşite. Mai mult , încapsularea este cea care permite crearea unui obiect . Obiectul este o entitate logică ce încapsulează atât date cât şi cod care manevrează aceste date. Într-un obiect o parte din cod şi / sau date pot fi particulare acelui obiect şi inaccesibile pentru orice din afara sa. În acest fel, un obiect dispune de un nivel semnificativ de protecţie care împiedică modificarea accidentală sau utilizarea incorectă a părţilor proprii obiectului de către secţiuni ale programului cu care nu are legătură .

- Moştenirea – permite definirea unor noi clase pornind de la cele existente. Marele avantaj al moştenirii este obţinerea prin derivare a datelor şi metodelor în clasa creată. Caracteristicile moştenite de clasa creată pot fi ajustate după necesităţi. Este posibil de asemenea derivarea unei clase din mai multe clase de bază (moştenire multiplă). Prin moştenire se pot obţine obiecte specializate pe baza unor obiecte mai generale. Avantajul apare când, pentru a crea un obiect, nu mai trebuie să pornim de la zero, ci putem deriva obiectul dintr-o clasă care are proprietăţi comune cu obiectul care dorim să-l obţinem.

- Polimorfismul - este caracteristica ce permite unei interfeţe să fie folosită cu o clasă generală de acţiuni. Acţiunea specifică selectată este determinată de natura precisă a situaţiei. Un exemplu din practica zilnică pentru polimorfism este un termostat . Nu are importantă ce combustibil se întrebuinţează pentru încălzirea casei (gaze , petrol , electricitate etc.) , termostatul lucrează în acelaşi fel. În acest caz termostatul (care este interfaţa) este acelaşi indiferent de combustibil (metoda). Acelaşi principiu se poate aplica şi programării. De exemplu , putem avea un program care defineşte trei tipuri de memorie stivă. Una este folosită pentru valori întregi, una pentru valori tip caracter şi una pentru valori in virgulă mobilă . Datorită polimorfismului , putem crea trei perechi de funcţii numite pune ( ) şi scoate ( ) – câte una pentru fiecare tip de

Page 9: Programare Orientata Pe Obiecte

8

date. Conceptul general ( interfaţă ) este cel de a pune şi de a scoate date dintr-o memorie stivă. Funcţiile definesc calea specifică (metoda) care se foloseşte pentru fiecare tip de date. Când punem date în memoria stivă , tipul de date va fi cel care va determina versiunea particulară a lui pune ( ) care va fi apelată. Polimorfismul ajută la reducerea complexităţii permiţând aceleiaşi interfeţe să fie folosită pentru a specifica o clasă generală de acţiuni. Rolul compilatorului este să aleagă acţiunea specifică (metoda) care se aplica fiecărei situaţii. Programatorul nu trebuie să execute personal aceasta acţiune. Nu trebuie decât să-şi amintească şi să folosească interfaţa generală.

- Modularitatea – posibilitatea grupării claselor în module. Un mediu creat pentru realizarea aplicaţiilor Windows este Visual C++, fiind cel

mai folosit compilator de C++ la ora actuală. El se bazează pe biblioteca de clase MFC (Microsoft Foundation Classes) ce permite crearea rapidă a unor cadre de aplicaţii pe baza conceptului de document-view sau de aplicaţie bazată pe un dialog.

2 PROGRAMARE ORIENTATĂ PE OBIECTE ÎN C++

2

2.1 Clase şi obiecte O clasă reprezintă un tip de date definit de programator. Dacă se foloseşte o

bibliotecă de clase existentă cum ar fi MFC, atunci programatorul poate defini direct obiecte de tipul acelei clase. Acest tip de date (class) permite declararea unor date protejate sau private, aceste date nefiind vizibile în afara clasei, nici măcar de către obiectele de tipul respectiv.

În C++, un obiect reprezintă o variabilă definită ca fiind de tipul clasei din care face parte.

Controlul accesului la componentele unei clase poate fi realizat utilizând specificatorii:

- public – membrul poate fi accesat de orice funcţie din domeniul declaraţiei clasei;

- private – membrul este accesibil numai de funcţii din interiorul clasei sau de către funcţii prietene (friend) ale clasei;

- protected – similar cu private dar accesul se extinde pentru funcţiile membre şi prietene derivate din clasa respectivă.

Page 10: Programare Orientata Pe Obiecte

9

Specificatorul de acces va fi utilizat în cadrul unei clase indicând numele acestuia urmat de “:”.

Eticheta public grupează membrii clasei pe care programul îi poate accesa cu operatorul punct. Pentru a ascunde detalii elementare ale unui obiect, C++ permite separarea definiţiei clasei în părţi private, protejate şi publice. Singura posibilitate de accesare a datelor şi metodelor private este prin intermediul metodelor publice. Pentru clasa de bază, obiectele derivate pot accesa membrii protejaţi, ca şi cum ar fi publici. În afara obiectelor derivate, însă , numai rutinele de interfaţă publice pot accesa membrii protejaţi.

O funcţie membră a unei clase are acces la toate datele membre din clasa respectivă, indiferent de specificatorul de acces.

Dacă nu specificăm tipul de acces, membrii vor fi private în mod implicit. Forma generală a declaraţiei unui tip class este similară cu a tipurilor struct din

C: class <nume_clasa> <:lista_clase_baza>{<lista_membri>}<lista_var>;

- nume_clasa este numele tipului clasă declarat şi trebuie să fie unic în domeniul în care este valabilă declaraţia.

- lista_clase_baza este lista claselor din care este derivată clasa declarată (dacă este cazul)

- lista_membrii reprezintă secvenţa de declaraţii ale membrilor clasei. Lista conţine declaraţii de date membre sau definiţii de funcţii membre. Datele membre pot fi de orice tip, mai puţin tipul de clasă declarat.

- lista_var este lista numerelor variabilelor de tip nume_clasa. Deşi nume_clasă şi lista_var apar opţionale, ca şi în cazul tipurilor de structură

din C, cel puţin una dintre ele trebuie să existe. De regulă nume_clasa nu se omite, pentru a pute declara ulterior obiecte de acest tip. La declararea obiectelor este suficient să se specifice numele clasei fără cuvântul cheie class.

Exemplu: definire clasă student. class Student { public: char Nume[100]; char Prenume[100]; int Varsta; protected: char Facultatea[30]; char Sectia[30]; int anul; int nr_restante; private: long media_ultim_an; double bursa; public: Student(); long aflaMedia(); void schimbaMedia(long MediaNoua);

Page 11: Programare Orientata Pe Obiecte

10

private: int Afla_nr_restante(); }; O clasă conţine: - o parte protejata/privata care asigura implementarea clasei; - o parte publica care reprezintă interfaţa clasei respective cu celelalte secţiuni

din program. Interfaţa la o clasa se referă la informaţia pe care utilizatorul trebuie să o

cunoască pentru a se putea folosi de facilităţile oferite de acea clasa. De aceea trebuie cunoscut: -identificatorii funcţiilor membru publice ale clasei; -prototipurile funcţiilor membru; -scopul funcţiilor membru. Protecţia membrilor intr-o clasa este indicata cu ajutorul unor specificatori de

acces/ modificatori de protecţie. Într-o clasa, la finalul cuvântului cheie, care precizează modul de acces, se afla

întotdeauna ":". Domeniul unui identificator de clasa este local şi începe din momentul declarării

clasei pana la finalul blocului respectiv. Daca o clasa este declarata în afara oricărei funcţii sau clase, domeniul sau este întregul fişier.

Domeniul membrilor unei clase coincide cu domeniu clasei respective. Daca prototipurile funcţiilor membru se afla în declaraţia clasei respective,

definiţiile lor (cu excepţia celor inline) sunt date în exteriorul declaraţiei clasei, folosind operatorul de specificare de domeniu "::".

Acesta indica faptul ca domeniul funcţiei respective este acelaşi cu domeniul clasei din care face parte.

Sintaxa definiţiei unei funcţii membre a unei clase: tip id_clasa :: id_func_membru(...){...}; Pentru a crea un obiect (dacă nu l-am specificat în definiţia clasei) de tipul unei

clase se foloseşte numele clasei ca şi cum ar un nume de tip. nume_clasă nume_obiect; O funcţie membră a clasei, sau metodă poate fi apelată doar printr-un obiect al

clasei. Se va realiza acest lucru folosind operatorul de apartenenţă punct. nume_obiect. În C++, obiectele unei clase se pot manipula folosind funcţiile membru şi

operatorii definiţi pentru acea clasa. O astfel de funcţie are, de regula, unul sau mai mulţi parametri şi returnează o

valoare. Mecanismul de transmitere prin valoare consta în primirea de către funcţie a

parametrilor actuali într-o stiva (stack) care este o structura de date de tip LIFO. Funcţia preia parametrii din stivă şi ii foloseşte în blocul sau fără a avea acces la locaţiile lor de memorie. Ea foloseşte doar copii locale ale acestor parametrii care vor fi eliminate din stiva la revenirea din funcţie. Prin urmare, printr-un astfel de mecanism, funcţia respectiva nu-si poate modifica parametrii cu care a fost apelata.

Page 12: Programare Orientata Pe Obiecte

11

Exemplu: void suma (int x){x+=100; } void main(){ int u=200; suma(u);//apel functie cout<<"u="<<u;} Se va observa că se afişează valoarea 200 (valoarea transmisa parametrului

formal al funcţiei) şi nu 300 cat ar trebui după execuţia funcţiei. Problema se poate rezolva folosind parametrii tip pointer. Exemplu: void suma (int *x){ *x+=100;} void main(){ int u=200; suma(&u);//apel functie cout<<"u="<<u;} În acest caz se va afişa pentru u valoarea 300. În C++, s-a arătat că se utilizează conceptul de referinţă, ce poate fi utilizat la fel

ca obiectul referit. Referinţa reprezintă adresa obiectului respectiv deosebindu-se de pointer prin faptul ca ea nu este o variabila reala. Ea este iniţializată în momentul definirii şi valoarea ei nu se poate modifica ulterior.

Transmiterea unei referinţe la un obiect evita efectuarea de copii inutile şi permite o implementare eficienta.

Referinţa se foloseşte în transmiterea de parametri, daca: a) funcţia trebuie să modifice parametrii actuali de la apel; b) funcţia nu modifica parametrii actuali, dar trebuie evitata copierea obiectelor

în stiva (economie de memorie). În acest ultim caz se va folosi specificatorul const pentru a indica faptul că parametrul transmis prin referinţă nu va fi modificat de funcţia respectivă.

2.2 Clase derivate Prin mecanismul de moştenire sau derivare se pot crea clase noi pe baza unor

clase existente. Clasa din care se moşteneşte se numeşte clasă de bază, clasa care moşteneşte se numeşte clasă derivată. Clasa derivată moşteneşte toţi membrii clasei de bază, dar nu va putea avea acces niciodată la membrii declaraţi private în clasa de bază. Practic, datele şi metodele se transferă de la clasa de bază la clasa derivată beneficiind deci de proprietăţi şi de un comportament asemănătoare cu cele ale clasei de bază.

Există şi conceptul de moştenire multiplă – o clasă derivată poate să moştenească de la mai multe clase de bază.

Exemplu: class A { private:

Page 13: Programare Orientata Pe Obiecte

12

int i2; }; class B: public A { public: int i3; }; Membrul i2 din clasa A, fiind declarat private, nu este vizibil în clasa derivată B,

indiferent de mofificatorul de acces.

2.3 Utilizarea variabilelor globale sau a funcţiilor globale în definirea funcţiilor membre a unor clase.

Într-o declaraţie de clasa se poate modifica accesul ori de cate ori este necesar. Tipurile struct şi union reprezintă cazuri particulare de clase care se distanţează

de conceptul OOP. O diferenţă esenţială intre class şi struct constă în faptul ca datele membru în

struct, în mod implicit, sunt publice, iar membrii din class, în mod implicit, sunt privaţi. Operatorul „::“ mai este numit şi operator de scop. Scopul şi accesibilitatea unui

identificator se pun în evidenta cu ajutorul acestui operator. Sintaxa: ::variabila operator; Exemplu: #include<iostream.h> #include<conio.h> int i=100; // declararea şi iniţializarea lui i ca variabila globala void f1(){ int i=9; // declararea lui i ca variabila locala /in funcţia f1 i++; // incrementarea variabilei locale i cout<<"i="<<i; cout<<"\n";} void f2(){ int i=5; // declararea lui i ca variabila locala în funcţia f2 ::i++; // incrementarea lui i global deşi este „mascat“ de declararea //unui i local int k=::i; cout<<"k="<<k;} void main() {clrscr(); f1(); f2();}

Page 14: Programare Orientata Pe Obiecte

13

Se observă că în C++, variabilele locale pot fi create în orice poziţie din cod. Se pot folosi, când este util, nume de funcţii standard de biblioteca (read, write, fopen etc.) drept nume de funcţii utilizator, urmând a fi reutilizate intr-o anumita maniera.

Exemplu: //definirea funcţiei utilizator int A::fopen(char*pn="fis.dat", char*pa="rt") { //…………………………….. ::fopen(pn, pa);// apel la functie de biblioteca ………………… } Din urmatorul exemplu reiese cum se poate utiliza operatorul "::" în cadrul unei

functii membru ce opereaza cu variabile membru şi cu variabile globale. Exemplu: #include<iostream.h> #include<conio.h> int v=20; // declarare variabila globala cu domeniul de tip fisier care incepe din

acest punct class B{ int v; // declararea unei variabile membru /privata a clasei public: void init(int x){v=x;} void f(); //declarare functie membru (prototip) }; void B::f(){ //definitia functiei membru int u=v++; //postincrementarea variabilei membru v cout<<"u="<<v; ::v++; //postincrementarea variabilei globale v int r =::v; cout<<"\n"; cout<<"r="<<r; } void main(){ B c; clrscr(); c.init(10); c.f(); } Un membru al unei clase poate fi definit prin construcţia nume_clasa::membru Una din motivatiile prezentei identificatorului clasei în fata operatorului "::" este

impusa şi de faptul ca trebuie să se distinga functiile cu acelasi identificator care apartin la clase diferite.

În plus, se permite operarea directa cu variabila membru, fara precizari suplimentare cu privire la identitatea sa. Mai exact, toate variabilele folosite intr-o functie membru a unei clase şi nedeclarate în ea, se presupun, în mod implicit, ca fiind membru ale acelei clase. Daca prin procesul de compilare nu se stabileste aceasta, se va trece la identificarea lor ca variabile globale.

Page 15: Programare Orientata Pe Obiecte

14

Apelul variabilei globale într-o funcţie se va realiza utilizând operatorul de rezoluţie. Exemplu: class numarator { double v: // variabila locala void setare(int cs,int cz,int cu); ................}; void numarator::setare(int cs,int cz,int cu) {………. ::v=v+1; //sau ::v+=1; utilizare variabila globală

la fel pentru functii: ::puts(mesaj); // utilizare funcţie globală

În acest fel se poate face deosebirea între doua funcţii sau variabile, ce utilizează aceiaşi notaţie(una globală şi una locală).

2.4 Funcţii inline Funcţiile mici, cu puţine instrucţiuni, apelate frecvent se compilează ineficient

(se rezervă spaţiu în stivă pentru parametrii şi rezultat, se efectuează salt la funcţie şi apoi salt pentru revenire). Mult mai eficientă ar fi expandarea apelului funcţiei prin corpul ei, ca în macroinstrucţiuni.

Apelul unei funcţii declarate inline este înlocuit la compilare prin corpul funcţiei (apel realizat prin expandare).

O funcţie inline nu trebuie să conţină bucle. Funcţiile membre definite în interiorul clasei sunt în mod implicit inline, dar este

posibil ca şi funcţii definite în afara clasei să fie declarate explicit inline (expandate). class Data{ public: int zi(); . . . private: int a, l, z; }; // functie declarata în mod explicit inline inafara clasei inline int Data::zi(){return z;}; // definirea clasei – fişierul punct.h class punct{ private: double x0, y0; // date membre public: inline void init(double x=0, double y=0); //nu era necesara inline void setx(double x=0){ x0=x; }; //declararea explicita inline void sety(double y=0){ y0=y; }; //erau considerate inline double getx(){ return x0; }; // este implicit inline

Page 16: Programare Orientata Pe Obiecte

15

double gety(){ return y0; }; void afisare(); }; // fişierul punct.cpp #include <iostream.h> #include <iomanip.h> //desi este definita în afara clasei este inline (apel expandat) inline void punct::init(double x, double y) { x0 = x; y0 = y; }; //nu este inline (apel=salt cu revenire) void punct::afisare(){ long f = ios::fixed; cout << setiosflags(f) << setw(6) << setprecision(2); cout << “(“ << x0 << “,” << y0 << “)” << endl; }; };

2.5 Functii de tipul prieten friend

O funcţie de tip friend este în esenţă o funcţie standard, care nu este membra a clasei ci are numai acces la membrii de tip privat ai acelei clase.

Iată o clasa denumită casa class casă { double data; public: double intalniri ; void Anca(intalniri); friend double musafiri(casă*p); };

Declaraţia unei funcţii prietene se poate face oriunde în cadrul declaraţiei clasei şi oferă funcţiei de a avea acces la oricare dintre membri. Funcţia rămâne externă, deci nu este permis un apel asociat unui obiect, de forma x.fct( ) sau x ->fct( ). Sunt posibile următoarele situaţii:

- funcţie independenta este prietena a unei clase; - funcţie membră a unei clase este prietena altei clase; - funcţie este prietena mai multor clase; - toate funcţiile unei clase y sunt prietene ale altei clase x; în acest caz se spune

ca clasa y este prietena a clasei x.

Page 17: Programare Orientata Pe Obiecte

16

2.6 Constructori şi destructori Iniţializarea asigurată de funcţia membru init() este lăsată la latitudinea

utilizatorului. Este de preferat o iniţializare mai sigură a obiectului, care să se facă în mod implicit la declararea obiectului.

Iniţializarea obiectelor se face prin intermediul unor funcţii speciale numite constructori.

Folosirea funcţiei initD() pentru iniţializarea obiectelor clasei Data este nesigură – putem uita să facem iniţializarea sau să facem o iniţializare repetată.

Un obiect poate fi declarat neiniţializat sau iniţializat. Exemple: Complex z; // obiect neiniţializat Complex z1(-1.5, 2.); // obiect iniţializat O soluţie sigură o reprezintă declararea unei funcţii având scopul explicit de

iniţializare a obiectelor. O asemenea funcţie poartă numele de constructor. Constructorul este o funcţie publică, care iniţializează datele membre, având

acelaşi nume cu numele clasei şi care nu întoarce nici o valoare. class Data{ . . . public: Data(int, int, int); // constructorul . . . }; Considerăm definiţia clasei Complex: #include <stdio.h> class Complex{ private: double re, im; public: void scrie(); }; void Complex::scrie(){ printf(“(%4.1lf , %4.1lf)\n”, re, im); }; void main(){ Complex z; //obiect neiniţializat z.scrie(); } Programul este corect din punct de vedere sintactic. Lipsa unui constructor

definit de utilizator, care să iniţializeze obiectele este remediată de compilator. Astfel, pentru clasa C, dacă nu se defineşte în mod explicit un constructor, compilatorul generează în mod implicit constructorul: C::C(){}, care creează datele membre ale

Page 18: Programare Orientata Pe Obiecte

17

clasei. Acest constructor implicit nu face nici o iniţializare a datelor membre, aşa că se vor afişa nişte valori foarte mari, reprezentând conţinutul memoriei neiniţializate.

Pentru a evita aceasta, se recomandă programatorului să îşi definească un constructor implicit (fără parametri), care să iniţializeze datele membre (în exemplul nostru iniţializarea la 0 pentru partea reală şi cea imaginară a obiectului număr complex). Constructorul implicit acţionează asupra obiectelor neiniţializate.

Clasa Complex, prevăzută cu acest constructor este: class Complex{ private: double re, im; public: Complex(){re=0.; im=0.}; void scrie(); }; Se preferă scrierea constructorului, folosind o listă de iniţializare, conform

sintaxei: Complex() : re(0.), im(0.){}; De această dată, în urma execuţiei se va afişa ( 0.0 , 0.0). Dacă se declară obiecte iniţializate, iniţializarea acestora trebuie făcută de către

un constructor de iniţializare, care trebuie definit în mod explicit de către utilizator. Pentru clasa Complex, un constructor de iniţializare, are forma: class Complex{ private: double re, im; public: Complex(double x, double y) : re(x), im(y){}; void scrie(); }; void main(){ Complex z1(-1., 2.); //obiect initializat Complex z2; //obiect neinitializat . . . } Dacă se declară un constructor de iniţializare, compilatorul nu mai generează un

constructor implicit, astfel că pentru obiectele neiniţializate (în exemplul de mai sus z2)şi tablourile de obiecte se va genera eroare. Putem defini deci doi constructori (şi în general oricâţi, având în mod evident semnături diferite), unul implicit şi celălalt de iniţializare:

Complex() : re(0.), im(0.){}; //constructor implicit Complex(double x, double y):re(x),im(y){}; //ctor de initializare Clasa de mai jos defineşte mai mulţi constructori: class Data{ int a, l, z; public: Data(int, int, int); // a, l, z Data(int, int); // l, z Data(int); // z

Page 19: Programare Orientata Pe Obiecte

18

Data(); // implicit cu data curenta Data(const char*); // dată reprezentată de şir . . . }; Se preferă înglobarea constructorului implicit în constructorul de iniţializare,

folosind parametri cu valori implicite. Constructorul de iniţializare poate fi folosit drept constructor implicit, dacă toţi parametrii sunt prevăzuţi cu valori implicite.

Complex(double x=0., double y=0.):re(x),im(y){}; Folosirea argumentelor implicite poate reduce numărul de constructori. În exemplul următor, în lipsa argumentelor, constructorul asigură iniţializarea

datei cu valori preluate din obiectul static azi. class Data{ int a, l, z; public: Data(int aa=0, int ll=0, int zz=0); . . . }; Data::Data(int aa, int ll, int zz){ a = aa? aa : azi.a; l = ll? Ll : azi.l; z = zz? zz : azi.z; }; Utilizatorul este obligat să-şi definească proprii constructori numai în situaţiile

în care obiectul alocă dinamic spaţiu în memorie. Nu pot fi definiţi mai mulţi constructori impliciţi .(fiind supraîncărcaţi, diferiţii

constructori trebuie să aibă semnături diferite). Un constructor cu un singur argument defineşte o funcţie pentru conversia de tip

de la tipul argumentului la tipul clasei. Deci vom putea iniţializa un obiect printr-o atribuire de forma:

clasa ob=argument; Atribuirea de mai sus poate fi considerată ca o conversie implicită. Pentru a o

dezactiva, constructorul cu un argument trebuie să fie precedat de cuvântul cheie explicit.

Operaţia de eliminare a unui obiect din memorie este realizată de o funcţie destructor, având acelaşi nume cu al clasei, fără argumente, care nu întoarce nici o valoare. Pentru a face distincţie faţă de constructor, numele destructorului începe cu ~.

Un destructor pentru un obiect este apelat în mod implicit la ieşirea din domeniul de definiţie al obiectului sau la sfârşitul programului, pentru obiectele globale sau statice. Sunt foarte rare situaţiile în care programatorul apelează explicit un destructor. Dacă o clasă nu are definit destructor, compilatorul generează un destructor implicit.

Programatorul nu apelează în mod explicit constructorii sau destructorul. Constructorii se apelează în mod implicit la declararea obiectelor. Destructorul este apelat implicit la ieşirea din blocul în care este declarat obiectul.

Obiectele create (cu new[])de către un constructor explicit trebuiesc distruse (cu delete []) de către un destructor definit în mod explicit.

La definirea unei funcţii constructor sau destructor nu trebuie definit tipul rezultatului întors de funcţie (nici măcar void).

Page 20: Programare Orientata Pe Obiecte

19

Iniţializarea obiectului de către constructor se poate face prin iniţializarea datelor membre cu valorile parametrilor sau prin copierea datelor membre ale unui obiect transmis ca parametru. În acest din urmă caz, avem de a face cu un constructor de copiere.

Un obiect poate fi iniţializat cu o copie a unui alt obiect al clasei sub forma clasa ob1(ob2);

De exemplu: Complex z1(1., -2.), z2(z1); O operaţie de tipul Clasa ob = Clasa(initializare) prin intermediul

constructorului, creează un obiect şi îi iniţializează datele membre. Exemplu: Complex z2 = Complex(1., -2.); are acelaşi efect cu declaraţia precedentă. Spre deosebire de acesta: Complex z2; Z2 = Complex(1., -2.); creează un obiect temporar, pe care îl copiază într-un obiect existent. Copierea unui obiect folosind operatorul de atribuire realizează o copiere

membru cu membru a obiectului sursă (din dreapta) în obiectul destinaţie (din stânga) Data d=d1; //atribuire Data *pd = new Data(d1); //creare obiect anonim iniţializat Copia se face membru cu membru. Obiectul ce urmează a fi copiat este transmis

prin referinţă. Compilatorul va genera pentru clasa C, un constructor implicit de copiere: C::C(const C&); care realizează copierea datelor membru cu membru (copiere superficială). Exemplu:

punct p1;// apelează constructorul implicit scris de programator // care asigura iniţializarea la (0, 0) punct p2(2,5); //iniţializare explicita (constructor de iniţializare) punct p3(p2); //iniţializare prin copiere (constructor de copiere) Constructorul implicit de copiere nu funcţionează corect în caz că obiectul alocă

dinamic memorie. În acest caz programatorul trebuie să-şi definească propriul constructor de copiere, care să realizeze o copiere profundă, a datelor indicate de pointeri şi nu o copiere a pointerilor.

Obiectele clasei pot fi copiate prin atribuire. Copierea se face membru cu membru. Exemplu:

Complex z1(-1., 2.); Complex z = z1; Data d1=Data(2004,10,5); Iniţializarea este mai eficientă decât atribuirea. În cazul atribuirii există o valoare

(veche) a obiectului, care trebuie ştearsă şi prin atribuire se copiază valoarea nouă a obiectului din dreapta operatorului de atribuire.

Vom considera un exemplu în care se alocă dinamic memorie pentru datele membre. Fie clasa persoana, prevăzută cu mai mulţi constructori şi un destructor:

class persoana{ private: char* nume; char* adresa; char* cnp; public:

Page 21: Programare Orientata Pe Obiecte

20

persoana(char* n=””,char* a=””,char* c=””); persoana(persoana& p); ~persoana(); }; persoana::persoana(char* n, char* a, char* c){ nume=new char[strlen(n)+1]; strcpy(nume, n); adresa=new char[strlen(a)+1]; strcpy(adresa, a); cnp=new char[strlen(c)+1]; strcpy(cnp, c); }; persoana::persoana(persoana& p){ nume=new char[strlen(p.nume)+1]; strcpy(nume, p.nume); adresa=new char[strlen(p.adresa)+1]; strcpy(adresa, p.adresa); cnp=new char[strlen(p.cnp)+1]; strcpy(cnp, p.cnp); }; persoana::~persoana(){ delete [] nume; delete [] adresa; delete [] cnp; }; Dacă o clasă conţine date membre constante sau referinţe, acestea nu pot fi

iniţializate în corpul constructorului, ci printr-o listă de iniţializare plasată după antetul constructorului şi separată de acesta prin

Constructorii şi destructorii sunt apelaţi implicit la definirea, respectiv distrugerea obiectelor. Obiectele sunt construite în ordinea declarării lor şi sunt distruse în ordine inversă declarării.

Clasele care conţin membri const şi membri referinţe trebuie să aibe un constructor definit de programator, întrucât referinţele trebuiesc iniţializate.

În absenţa unor declarări explicite, compilatorul furnizează următoarele funcţii membre:

- un constructor implicit - un constructor de copiere - un operator de atribuire - un destructor implicit - un operator de adresare

2.7 Referinţe În C++ se păstrează lucrul cu pointeri, care permite un acces rapid la date.

Page 22: Programare Orientata Pe Obiecte

21

Noţiunea de referinţă s-a introdus pentru a simplifica transferul parametrilor şi al valorilor de return prin adresă.

Pentru referirea la membri se foloseşte „.” Chiar dacă referinţa este practic adresa obiectului. Metoda poate fi folosită pentru class, struct, union.

2.8 Membrii statici ai unei clase Membrii unei clase care sunt declaraţi cu acest specificator se numesc statici şi

au un rol aparte în cadrul clasei. Pentru datele nestatice ale unei clase există copii distincte în fiecare obiect.

Datele statice există într-o singură copie, comună tuturor obiectelor. Crearea, iniţializarea şi accesul la această copie sunt total independente de obiectele clasei.

Funcţiile membre statice efectuează operaţii care nu sunt asociate obiectelor individuale, ci întregii clasei.

Un membru static poate fi referit de funcţiile ne-membre prin două metode: Indicând numele clasei şi folosind operatorul de rezoluţie de domeniu; Specificând un obiect al clasei şi folosind operatorul de selecţie „ .”. class A { static int i; }; int A::i; main() { A::i=10; A od; int n = od.i; }

2.9 Sistemul de I/E din C++ Deşi limbajul C++ conţine toate rutinele din biblioteca de I/E a limbajului C, el

ne pune la dispoziţie şi un sistem propriu de I/E orientat pe obiecte, care ne ajută să citim/scriem obiecte de diferite tipuri. Principalul avantaj al sistemului de I/E din C++ constă în faptul că el poate fi redefinit în cadrul propriilor clase.

Ca şi în limbajul C, sistemul de I/E din C++ nu face deosebire între operatori care folosesc consola şi cele care utilizează fişiere.

Sistemul de I/E din C++ operează prin “streams”. La execuţia unui program scris în C++, următoarele patru streams (drivere) sunt

predefinite: cin intrare standard - tastatură cout ieşire standard - ecran

Page 23: Programare Orientata Pe Obiecte

22

cerr eroare standard - ecran clog versiunea tampon a lui cerr - ecran Acestea fluxuri de I/E au prototipul în iostream.h. Utilizând aceste fluxuri de intrare/ieşire datele sunt citite sau scrise prin

intermediul operatorilor << şi >> : #include <iostream.h> void main() { int n; char sir[20]; cout << "Introduceţi un număr: " << '\n'; cin >> in; cout << " şi un sir :" << '\n'; cin >> sir; cout << " Numărul este :" << n << "\n iar şirul este :" << sir << '\n\'; } Programul citeşte un număr şi un sir de pe driverul cin (de obicei tastatura) şi

afişează aceste date pe cout (monitor). Putem face următoarele observaţii: - driverele cout, cin şi cerr sunt de fapt 'obiecte' dintr-o anumită clasă care

procesează intrarea şi ieşirea unui program; - driverul cin citeşte datele şi copie informaţiile în variabile folosind

operatorul >> (deplasare dreapta). Operatorii ce manipulează cin, cout şi cerr (adică >> şi << ) manipulează în

acelaşi timp şi variabile de diferite tipuri. Acţiunea lor depinde în final de tipul datelor. Driverele cin, cout şi cerr nu fac parte din gramatica lui C++, aşa cum este ea

definită de compilatorul ce prelucrează fişierul sursa. Definiţiile lor apar doar în fişierul header iostream.h. Utilizarea vechilor funcţii printf() şi scanf(), raportate la cin şi cout implică anumite dezavantaje.

Comparat cu funcţiile standard C, printf () şi scanf (), folosirea operatorilor << şi >> împreuna cu driverele corespunzătoare este mult mai sigură în ceea ce priveşte tipul datelor (formatul lor este descris de programator în primul caz şi este recunoscut de compilator în cel de-al doilea)

Funcţiile printf () şi scanf (), precum şi alte funcţii ce folosesc formatul explicit, implementează în fond un mini-limbaj care este interpretat numai la execuţie. Din contra, compilatorul C++ cunoaşte exact ce acţiune de intrare/ieşire trebuie să efectueze.

Clasa care gestionează funcţionarea de bază a unui stream prin funcţii şi variabile membre este IOS. Aceasta asigură operaţiile cu format, verificarea erorilor şi a informaţiilor de stare. IOS este folosită drept clasă de bază pentru următoarele trei clase: ISTREAM, OSTREAM şi IOSTREAM.

Folosind sistemul de intrare/ieşire din C++, se pot formata datele într-un mod similar funcţiei printf().

Fiecare stream din C++ are asociat cu el un număr de “indicatori de format” (flags), care determină modul de afişare; ei sunt codificaţi într-o variabilă de tip “integer”. Tipul de enumerare următor definit în clasa IOS, conţine constantele corespunzătoare acestor “indicatoare”.

Page 24: Programare Orientata Pe Obiecte

23

enum { skipws = 0 x 0001 left = 0 x 0002 right = 0 x 0004 internal = 0 x 0008 dec = 0 x 0010 oct = 0 x 0020 hex = 0 x 0040 showbase = 0 x 0080 showpoint = 0 x 100 uppercase = 0x 200 showpos = 0 x 400 scientific = 0 x 800 fixed = 0 x 1000 unitbuf = 0 x 2000 stdio = 0 x 4000 }; Când se poziţionează un flag de format, este activată caracteristica respectivă;

când el este şters, se utilizează formatul implicit. La operaţia de citire când este fixat flag-ul: - skipws – se ignoră caracterele de tip whitespace (spaţiu, tab, newline), când

este şters caracterele nu mai sunt ignorate. - left – se aliniază rezultatul la stânga. - right – se aliniază rezultatul la dreapta. - internal – se extinde o valoare numerică astfel încât să se completeze

câmpul, prin inserarea de spaţii între caractere de bază. - oct – determină afişarea în sistem octal. - hex – determină afişarea în sistem hexazecimal. - dec – se revine în sistem zecimal. - showbase – generează afişarea ieşirii în baza de numeraţie corespunzătoare.

1F va fi afişat 0 x 1F - showpoint – determină afişarea tuturor zerourilor şi al punctului zecimal ale

unei valori în format virgulă mobilă. - showpos – determină afişarea semnului, înaintea valorilor zecimale pozitive. - scientific – generează afişarea numerelor în format virgulă mobilă,

folosindu-se notaţia ştiinţifică, f. exp. - unitbuf – goleşte tamponul după fiecare scriere. - fixed – valorile în format virgulă mobilă sunt afişate folosindu-se notaţia

normală (cu punct fix). - stdio – când este poziţionat indicatorul stdio , fiecare stream este eliberat

după o operaţie de ieşire, ceea ce determină scrierea în dispozitivul fizic cuplat la stream.

- uppercase – afişare cu majuscule. Pentru a fixa un flag de format, utilizăm funcţia setf() care este membră a clasei

IOS. Sintaxa este: long setf (long, flags);

Page 25: Programare Orientata Pe Obiecte

24

Această funcţie restaurează starea anterioară a indicatorilor, şi activează flag-urile specificate de argumentul flags.

De exemplu petru a activa flag-ul showpos, putem folosi instrucţiunea: STREAM.setf (ios ::showpos);

STREAM se referă la streamul afferent (cout sau cin) Setf() – este o funcţie membră a clasei IOS, şi efectuează stream-ul creat de

aceasta. Orice apel al funcţiei setf() se referă la stream-ul corespunzător. Fiecare stream conţine informaţiile de stare a propriului format. Prin aplicarea

unui operator de tip OR valorilor, putem poziţiona mai mulţi indicatori de stare. De exemplu, apelul de mai jos poziţionează flag-urile showbase şi hex;

cout.setf(ios :: showbase | ios :: hex); Întrucât indicatorii de format sunt definiţi în clasa IOS, trebuie să obţinem

accesul la ei prin utilizarea operatorului de rezoluţie :: şi a numelui de clasă. Funcţia unsetf() este complementară funcţiei setf(). Pentru a afla stările curente

ale indicatorilor se foloseşte funcţia flags(), membră a clasei IOS. Aceasta înapoiază starea curentă a fiecărui flag de format.

Prototipul ei este: long flags(); funcţia flags() are o a doua versiune, care ne permite să poziţionăm toţi

indicatorii (asociaţi unui sistem), la valorile specificate de argumentul ei. Prototip: long flags (longt); Copiază şablonul de biţi din variabila t în variabila utilizată. Aplicaţie: #include <iostream.h> main() { cout << 123.33 <<”salut!” <<100<<”\n”; cout << 10 <<”” << -10 << “\n”; //schimbăm formatul cout.setf(ios :: hex | ios :: scientific); cout << 133.33 << ”salut!” << 100 << “\n\n”; cout.setf(ios :: showpos); cout <<10 << “ “ << -10 << “\n”; cout.setf (ios :: showpoint); cout << 100.0; return 0; }

Page 26: Programare Orientata Pe Obiecte

25

2.10 Utilizarea funcţiilor width(), precision() şi fill() Există trei funcţii membre ale clasei IOS, care poziţionează parametrii de format

(lăţimea câmpului, precizia, caracterul de inserat) Când este afişată o valoare, ea ocupă doar spaţiul rezervat numărului de

caractere necesar. Totuşi putem specifica o lăţime minimă a câmpului, folosind funcţia width() al cărui prototip este următorul:

int width(int w); w – este lăţimea câmpului, iar valoarea înapoiată este anterioară a câmpului. Dacă o valoare utilizează un spaţiu mai mic decât cel specificat, câmpul este

completat cu caracterul curent de inserat (implicit acesta este „spaţiu”). Dacă, totuşi mărimea valorii depăşeşte lăţimea minimă a câmpului, câmpul va fi suprascris şi valorile nu vor fi trunchiate. La afişarea unei valori în format de virgulă mobilă, se folosesc (în mod implicit) şase cifre după punct; totuşi, putem fixa acest număr dacă apelăm funcţia: precision () al cărei prototip este:

int precision (int p); Aici precizia este stabilită la valoarea dată de parametrul p şi funcţia va înapoia

vechea valoare. Stabilirea unui caracter alternativ utilizat pentru completarea câmpului se poate realiza prin funcţia fill(). Prototipul acesteia este:

char fill (char ch); ch – reprezintă noul caracter ce completează câmpul. - funcţia va returna vechiul caracter. Exemplu: #include <iostream.h> void main() { cout.width(10); //lăţimea câmpului de 10 cout << „salut” << ”\n”; cout.fill (”%”); cout.width(10); cout << ”salut” << ”\n”; cout.setf(ios :: left); cout.width(10); cout << ”salut” << ”\n”; cout <<123.234567 << ”\n”; cout.width (10); cout.precision(3); cout << 123.234567 << ”\n”; }

Page 27: Programare Orientata Pe Obiecte

26

2.11 Supraîncărcarea funcţiilor şi operatorilor Supraîncărcarea funcţiilor şi operatorilor (overloading) sunt elemente esenţiale

în programarea C++. Ele oferă o baza importanta pentru polimorfism în timpul compilării, dând o extensibilitate şi flexibilitate limbajului.

Supraîncărcarea unei funcţii înseamnă utilizarea aceluiaşi identificator de funcţie pentru cel puţin două funcţii cu condiţia ca ele să se deosebească prin tipuri si/sau număr de parametri, adică să aibă prototipuri diferite.

În acest fel compilatorul poate selecta, la un moment dat, funcţia care trebuie apelată.

In C++, se pot supraîncărca aproape toţi operatorii, astfel încât ei să efectueze diverse operaţii în mai multe clase create. La supraîncărcarea unui operator, nu se pierde nimic din semnificaţiile sale originale, ci doar se extinde tipul obiectelor cărora li se poate aplica. Supraîncărcarea operatorilor se referă la o facilitate specifica limbajului C++. Este vorba de a oferi posibilitatea programatorului să atribuie operatorilor şi alte sensuri decât cele predefinite.

Un operator binar supraîncărcat, de exemplu *, poate realiza operaţia de înmulţire şi intre obiecte de tip matrice, numere complexe, numere raţionale.

2.12 Funcţie operator

Un operator se poate supraîncărca prin definirea unei funcţii operator. O funcţie operator defineşte operaţiile specifice pe care le va efectua operatorul supraîncărcat corespunzător clasei în care operează.

Funcţiile operator pot fi sau nu funcţii membru ale clasei în care operează. De obicei, funcţiile operator, care nu sunt funcţii membru ale clasei, sunt funcţii friend.

Sintaxa unei funcţii operator membru a unei clase; tip_ret iden_clasa::operator semn_op(lista_par) { // operaţii} Cuvântul "operator" este un cuvânt cheie, iar la crearea unei funcţii operator,

semn_op reprezintă simbolul operatorului supraîncărcat. De regula, funcţiile operator membru returnează un obiect al clasei asupra căreia

operează şi nu sunt funcţii statice. Funcţia operator membru care supraîncarcă un operator binar conţine un singur

parametru. Motivul constă în faptul ca operandul din stânga operatorului binar este plasat

implicit funcţiei prin pointerul this care este ataşat funcţiei operator. Operandul din

Page 28: Programare Orientata Pe Obiecte

27

dreapta operatorului binar este transmis funcţiei operator prin parametrul existent în funcţie.

Apelul funcţiei operator este generat de obiectul ce reprezintă operandul stâng al operatorului binar respectiv.

Exemplu: Sa se implementeze clasa complex realizând operaţiile aritmetice adunare,

scădere, înmulţire şi împărţire prin supraîncărcarea operatorilor respectivi cu funcţii operator membru.

#include<iostream.h> #include<conio.h> #include<math.h> class complex{ double re,im; public: complex(double a=0.0,double b=0){re=a;im=b;} void cit(){ cout<<"Dati partea reala. ";cin>>re; cout<<"Dati partea imaginara. ";cin>>im; getch();} complex operator+(complex &c1); complex operator-(complex &c2); complex operator*(complex &c3); complex operator/(complex &c4); void afis(){ if(im<0)cout<<re<<im<<"*i"; else cout<<re<<"+"<<im<<"*i";} }; complex complex::operator+(complex &c1){ complex u; u.re=re+c1.re; u.im=im+c1.im; return u;} complex complex::operator-(complex &c2){ complex v; v.re=re-c2.re; v.im=im-c2.im; return v;} complex complex::operator*(complex &c3){ complex t; t.re=re*c3.re-im*c3.im; t.im=re*c3.im+im*c3.re; return t;} complex complex::operator/(complex &c4){ complex z; z.re=(re*c4.re+im*c4.im)/(c4.re*c4.re+c4.im*c4.im);

Page 29: Programare Orientata Pe Obiecte

28

z.im=(im*c4.re-re*c4.im)/(c4.re*c4.re+c4.im*c4.im); return z;} void main(){ complex z1,z2,z3; int n; clrscr(); z1.cit(); z2.cit(); z3=z1+z2; cout<<"\nSuma este: ";z3.afis(); z3=z1-z2; cout<<"\nDiferenta este: ";z3.afis(); z3=z1*z2; cout<<"\nProdusul este: ";z3.afis(); z3=z1/z2; cout<<"\nRaportul este: ";z3.afis(); getch();} Analizând funcţia operator-() se observa ca operandul din dreapta operatorului "-

" , conform semnificaţiei operaţiei de scădere, este scăzut din operandul stâng. Deoarece cel care generează apelul funcţiei operator este obiectul indicat de operandul stâng, datele operandului drept trebuie scăzute din cele corespunzătoare indicate prin pointerul this.

Exemplu: Sa se implementeze o clasa "sir" care să permită efectuarea unor operaţii cu

şiruri ca: iniţializare, copiere, atribuirea unui sir unui alt sir, afişarea lor şi să se supraîncarce operatorul "+" pentru concatenarea a doua şiruri.

#include<iostream.h> #include<conio.h> #include<string.h> class sir{ char *p; int l; public: sir(char*s); sir(int n=45); sir(const sir&); ~sir(); int ret_l(); void afis(); sir operator+(sir &s2); }; sir::sir(char*s){ l=strlen(s); p=new char[l+1]; strcpy(p,s);} sir::sir(int dim){ l=dim;

Page 30: Programare Orientata Pe Obiecte

29

p=new char[l+1]; *p='\0';} sir::sir(const sir&s){ l=s.l; p=new char[l+1]; strcpy(p,s.p);} sir::~sir(){ delete p; } int sir::ret_l(){ return l; } void sir::afis(){ cout<<"\n"<<p; } sir sir::operator+(sir &s){ sir suma(l+s.l); strcpy(suma.p,p); strcat(suma.p,s.p); return suma;} void main(){ clrscr(); sir s1("CONCURSUL DE "); sir s2("PROGRAMARE în C++"); sir s3; s3=s1+s2; s3.afis(); cout<<"\nSirul afisat are lungimea:"<<s1.ret_l(); getch();} O situaţie mai deosebita se întâlneşte la supraîncărcarea operatorului binar de

atribuire (asignare) „=“. Copierea datelor membre ale unui obiect sursa (reprezentat prin

operandul drept al operatorului de atribuire) în datele membre corespunzătoare ale obiectului destinaţie (reprezentat prin operandul stâng al operatorului de atribuire), se poate realiza în doua moduri:

1) iniţializare prin copiere id_clasa ob_d=ob_s; 2) atribuiri de obiecte id_clasa ob_s, ob_d; ob_d=ob_s; Iniţializarea prin copiere este însoţită de alocare de memorie şi se face cu

ajutorul constructorului de copiere. Acesta este apelat automat la instanţierea unui obiect printr-o declaraţie de forma 1). Daca un astfel de constructor nu exista în clasa respectiva, copierea se va face la nivel de bit.

Aplicaţie: Se va utiliza supraîncărcarea operatorilor binari + şi *, cu funcţii operator

membru. clasa matrice. #include <iostream.h> #include <stdio.h> #include <conio.h> #include<math.h> class matrice {

Page 31: Programare Orientata Pe Obiecte

30

private: int mat[10][10]; public: matrice(int i,int j); matrice(); matrice(matrice&); void afiseaza(); matrice operator+(matrice&); matrice operator*(matrice&); }; matrice::matrice(int r,int p) { int j; for(int i=0;i<r;i++) for(j=0;j<p;j++) { cout<<" mat["<<i<<"]["<<j<<"]="; cin >>mat[i][j]; } } int r; matrice::matrice(){ int j; for(int i=0;i<r;i++) for(j=0;j<r;j++) mat[i][j]=0; } matrice::matrice(matrice& m) { for(int i=0;i<r;i++) for(int j=0;j<r;j++) mat[i][j]=m.mat[i][j]; } void matrice::afiseaza(){ int i,j; for(i=0;i<r;i++){ cout<<"\n"; for(j=0;j<r;j++) cout<<mat[i][j]<<"\t"; } cout<<"\n"; } matrice matrice::operator+(matrice& k) { int i,j; matrice t; for(i=0;i<r;i++) for(j=0;j<r;j++) t.mat[i][j]=mat[i][j]+k.mat[i][j]; return t; } matrice matrice::operator*(matrice& k){ int u,v,i,rez; matrice t; for(u=0;u<r;u++) for(v=0;v<r;v++) { rez=0; for(i=0;i<r;i++)

Page 32: Programare Orientata Pe Obiecte

31

rez+=mat[u][i]*k.mat[i][v]; t.mat[u][v]=rez; } return t; } void main(){ clrscr(); cout<<"\n r=";cin>>r; matrice m(r,r),n; m.afiseaza(); n=m+m; n.afiseaza(); getch(); n=m*m; n.afiseaza(); getch();}

2.13 Supraîncărcarea operatorilor << şi >> Atunci când se doreşte ca streamurile să opereze cu alte tipuri decât cele

implicite cu structură simplă. Sau se doreşte să se realizeze o citire sau o scriere cu anumite formatări sau în anumite configuraţii se face apel la supraîncărcarea operatorilor.

Pentru streamul cout avem următoare secvenţă generală de supraîncarcare a

operatorului de inserţie: ostream &operator <<(ostream &stream_atasat,tip_clasa structura_mea) { // facem operatiile de formatare si afisare a structurii stream_atasat<<structura_mea.camp1<<” ”<<…; stream_atasat.setf(…); ……. //si acum returnam in mod obligatoriu streamul modificat return stream_atasat; } În program vom folosi supraîncărcarea astfel: tip_ clasa a,b,c; cout<<a<<b<<c; Cum funcţionează structura? Streamul cout de tip ostream (stream de iesire –

Output) este transmis ca parametru funcţiei de supraîncărcare a operatorului la fel şi conţinutul variabilei a (apoi într-o nouă apelare b, apoi c). Se lucrează asupra streamului transmis modificându-l, deoarece nu-i transmitem valoare ci însuşi adresa (operatorul &),

Page 33: Programare Orientata Pe Obiecte

32

La sfârşit streamul modificat este returnat de funcţie sistemului ce gestionează streamurile. Afişarea se face în momentul accesării streamului returnarea la sfârşit a acestuia de către funcţie asigurând retransmiterea adresei, o redundanţă specifică lucrului cu pointeri şi funcţii.

Pentru supraîncărcarea operatorului de extracţie care operează cu streamul cin se

va folosi structura: istream &operator>>(istream &stream_atasat,tip_clasa &struct_mea) { //se va lucra asupra streamului de intrare //se vor face formatarile streamului si va citi efectiv prin stream stream_atasat.setf(…) ; stream_atasat >>ws ; cout>>”Introduceti campul 1:”; stream_atasat >>struct_mea.camp1 ; stream_atasat >>struct_mea.camp2; …….. //se va returna obligatoriu streamul atasat return stream_atasat ; } La folosirea operatorului in program se va proceda astfel: tip_clasa a,b,c; cin>>a>>b>>c; Se observa că streamul cin este de tipul istream (stream de intrare – Input

stream). În plus se transmite adresa structurii prelucrate de stream pentru că structura este

modificată nu doar afişată ca la supraîncărcarea operatorului de inserţie. În rest abordarea este la fel ca la inserţie. Am numit operator de inserţie pe cel asociat cu streamul cout deoarece inserează, adaugă informaţie în streamul cout respectiv la ieşirea de tip display, şi extracţie deoarece extrage din streamul cin date şi le plasează în câmpurile structurii proprii specificate.

Exemplu: #include <iostream.h> #include <iomanip.h> //Avem structura struct persoana{ char nume[25] ; char prenume[25] ; int varsta ; double salariu; char nr_telefon[15] ; }

Page 34: Programare Orientata Pe Obiecte

33

//Vom defini un operator de insertie ostream &operator<<(ostream &display,persoana p) { display<<”Numele ”<<p.nume<<” ”<<p.prenume<<endl; display<<”Are ”<<p.varsta<<” ani.”<<endl; display<<”Are un salariu de ”<<p.salariu<<”RON ”<<endl; display<<”O puteti apela la nr.”<<p.nr_telefon<<endl; return display; } //Vom defini un operator de extractie istream &operator<<(istream &tastatura,persoana &p) { cout<<”Numele:”; tastatura>>p.nume; cout<<”Prenumele:”; tastatura>>p.prenume; cout>>”Varsta :”: tastatura>>p.varsta; cout>>”Salariul actual [RON]:”; tastatura>>p.salariu; cout>>”Nr. de telefon la care poate fi gasita:”; tastatura>>p.nr_telefon; return tastatura; } void main() { persoana a; cin>>a; cout<<”Datele introduse despre persoana sunt:”<<endl<<a; }

2.14 Definirea de manipulatori personalizaţi Atunci când dorim să realizăm rapid sau să grupăm sub o singură comandă

anumite opţiuni de formatare pe care le folosim repetat sau pur şi simplu dorim să dăm un nume sugestiv acestor opţiuni vom face apel la definirea manipulatorilor. În anumite puncte structura de redefinire va fi asemănătoare structurii de supraîncărcare, dar sunt şi element specifice.

Pentru un manipulator de ieşire asociat funcţiei cout vom folosi structura: ostream & nume_manipulator_out(ostream &stream_atasat) {

Page 35: Programare Orientata Pe Obiecte

34

//operatii de formatare stream_atasat.setf(..); //operatii de afisare stream_atasat<<…<<…; // in final obligatoriu vom returna streamul atasat return stream_atasat; } In program îl vom folosi astfel: cout<<nume_manipulator_out<<...<<...; Pentru un manilator de intrare asociat functiei cin vom folosi următoarea

structură de declarare: istream & nume_manipulator_in(istream &stream_atasat) { //operatii de formatare //operatii de citire // in final in mod obligatoriu vom returna streamul atasat return stream_atasat; } Vom apela manipulatorul scriind : cin>>nume_manipulator_in>>v1>>…>>vk ; Trebuie avut grijă să nu folosim un manipulator de iesire în locul unuia de

intrare şi invers pentru că manipulatorii lucrează cu tipuri de streamuri diferite. Putem crea manipulatori parametrizaţi prin simpla adăugare în lista de parametri

a funcţiei de noi parametri, de exemplu: ostream &nume_manipulator(ostream &stream_atasat,tip1 p1,tip2 p2,…tipn

pn) { …. } la apelare vom proceda astfel: tip1 a1; tip2 a2; .... tipn an; cout<<nume_manipulator(a1,a2,...an); Pentru manipulatorii de intrare se va proceda la fel.

Page 36: Programare Orientata Pe Obiecte

35

2.15 Lucrul cu fişierele în C++ După cum există o formatare pe bază de streamuri a intrărilor şi ieşirilor există

şi o modalitate de lucru cu fişierele bazată pe streamuri. Astfel fiecărui fişier i se va asocia un stream în care fi se va putea numai scrie şi citi, fie ambele. De asemenea există o abordare secvenţială în lucrul cu fişierele( adică pentru a citi secvenţa dorită trebuie să parcurgem prin citire tot ce este înaintea ei) şi o abordare de acces aleatoriu (în care citim sau scriem în orice moment exact secvenţa care ne interesează dacă în schimb îi ştim poziţia precisă în fişier). Mai mult putem accesa fişierele în format text ca o colecţie de şiruri de caractere sau în format binar ca o grupare de octeţi.

Întotdeauna în lucrul cu fişierele vom avea obligatoriu două elemente – o funcţie de deschidere a fişierului (open(…)) şi funcţie de închidere fără parametri (close()).

Lucrul secvenţial cu fişierele Îl vom utiliza când lucrăm cu fişierele text şi nu avem nevoie de optimizări fie

pentru că fişierul este prea mic, fie pentru că optimizarea nu şi-ar avea rost. Avem trei tipuri de streamuri de tip fişier: - ifstream – pentru scriere în fişier; - ofstream - pentru citire din fişier; - fstream – pentru scriere şi citire din fişier;

Deschidem streamul asociat fişierului cu funcţia open() ce are următorul

prototip: void open(const char *numefisier,int mod,int acces);

În general vom avea nevoie doar de numele fişierului putând scrie doar: ofstream iesire; iesire.open(”test.txt ”);

Datele ce vor fi scrise în fişier sunt scrise ca şi cum s-ar lucra cu cout numai că datele nu vor fi afişate pe ecran ci scrise în fişier cu formatările

specifice streamului ieşire.De exemplu: iesire<<” Salariu ”<<salariu_net<<” RON”; Dupa ce am terminat lucrul cu fişierul îl vom închide cu: iesire.close();

Page 37: Programare Orientata Pe Obiecte

36

La fel se va lucra şi cu ifstream, după cum se lucrează cu cin, pentru a se citi

nişte date din fişier.

2.16 Prelucrare binară a fişierelor Avem funcţiile membre asociate streamurilor fişier : - get(char &ch) asociată ifstreamurilor; - put(char ch) asociată ofstreamurilor.

Pentru a citi un caracter din fişier vom avea secvenţa:

ifstream intrare; intrare.open(”in.txt”,ios::in|ios::binary); char c; intrare.get(c); intrare.close(); Pentru a scrie un caracter în fişier vom avea secvenţa: ofstream iesire; iesire.open(”aut.txt”,ios::out|ios::binary); iesire.put(’a’); iesire.close(); Pentru a scrie dintr-o zonă tampon în stream sau invers se folosesc funcţiile

write(…)şi read(…) cu următorul prototip: istream &read(unsigned char *buf,int ncitit); ostream &write(const unsigned char *buf,int nscrisi); read() va citi din stream ncitit octeţi şi-i va pune în zona de memorie buf. write() va citi din buf nscris octeţi şi-i va pune în stream. La deschidere se va seta indicatorul de access ios :: binary. Dacă se doreşte de exemplu scriere unei structuri oarecare : Struct persoana p ; iesire.write((unsigned char*) &p,sizeof(struct persoana)); La fel se va proceda şi la citire. Pentru a afla câte au fost citite cu read() se foloseşte funcţia membru gcount(),

un exemplu: intrare.read(…)ş

Page 38: Programare Orientata Pe Obiecte

37

cout<<intrare.gcount();// afiseaza numarul de caractere extrase din stream la // ultima citire

Pentru a citi o linie dintr-un fişier text se foloseşte funcţia getline() cu prototipul: istream &getline(char *buf,int nmaxcitit,char delimatator); Funcţia membru citeşte caractere din stream până când ori se întâlneşte

delimitatorul (care este implicit linie nouă) ori s-au citit nmaxcitit caractere. Oricare din condiţii întâlnită prima determină oprire citirii.

Pentru a determina dacă s-a atins sfârşitul de fişier de foloseşte funcţia membru

eof() care este diferită de zero dacă da.(EOF = End Of File). Mai întotdeauna se foloseşte valoarea negată a funcţiei într-o secvenţă while când se parcurge cap coadă un fişier.

Pentru a fi siguri că informaţia a fost scrisă din stream în fişier, pentru

descărcarea zonei de memorie a streamului în fişier se foloseşte funcţia membru flush(). Aceasta poate fi folosită ca o măsură de siguranţă.

2.17 Lucrul aleatoriu cu fişierele Se poate avea acces aleatoriu în fişier prin folosirea funcţiilor membre de

poziţionare seekg(…) şi seekp(…) care au următoarele prototipuri: istream &seekg(streamoff offset,seek_dir origine); ostream &seekp(streamoff offset,seek_dir origine); Prima poziţionează în streamul de intrare, cea de-a doua în streamul de ieşire. Precizia de poziţionare este de un octet, poziţia precizându-se printr-o valoare

întreagă offset relativ la un punct de referinţă stabilit prin origine. Origine poate avea una dintre cele trei valori – ios::beg (Poziţia este faţă de

începutul fişierului), ios::cur(Poziţia este faţă de poziţia curentă a pointerului de fişier), ios::end(Poziţia este relativă faţă de sfârşitul de fişier).

De exemplu pentru fisierul deschis pentru scriere cu streamul iesire de tip ofstream pentru a ne poziţiona la 10 octeţi faţă de început vom scrie:

iesire.seekp(10,ios::beg); Mai avem funcţiile membre care ne dau poziţia curentă a pointerului de fişier

tellg() şi tellp() folosite împreună cu seekg() şi seekp().

Page 39: Programare Orientata Pe Obiecte

38

Este indicat ca la accesul aleatoriu fişierele să fie deschise cu indicatorul de acces ios::binary în plus la scrieri şi citiri în cadrul aceluiaşi fişier să se folosească fstreamul.

Toate referirile la istream sunt automat compatibile cu ifstream prin mecanismul

de moştenire, la fel şi pentru ostream cu ofstream. Ca specificatori suplimentari utili în lucrul cu fişierele ar fi: - ios::nocreate – dacă nu există fişierul va eşua deschiderea pentru citire; - ios::noreplace – dacă fişierul există acesta nu va fi suprascris, evitându-se

deschiderea; - ios::app – se foloseşte pentru deschiderea spre adăugare la sfârşitul fişierului

(append).

2.18 Cuvântul cheie this La apelarea unei funcţii membre, aceasta este informată asupra identităţii

obiectului asupra căruia va acţiona prin transferul unui parametru implicit care este adresa obiectului.

De exemplu, în cazul apelului: ob.verificare(i); funcţia verificare() primeşte şi adresa obiectului ob, în afară de

valoarea i. De asemenea există cazuri în care adresa obiectului este necesar să fie utilizată

în definiţia funcţiei. Acest lucru este realizat în C++ de cuvântul cheie this, asociat unui pointer către obiectul pentru care s-a apelat funcţia.

Cuvântul this are sens şi poate apare numai în definiţia unor funcţii membre. Exemplul următor adaugă clasei student o funcţie adresa(), care afişează adresa

obiectului: Exemplul 1: student::adresa() { cout<<”Adresa obiectului pentru care s-a apelat funtcia este”; cout<<this; } ............ student stEI; // Se defineşte un obiect stEI aparţinând clasei EI. stEI.adresa(); // Afişează adresa obiectului stEI. Exemplul 2 Vom crea două obiecte de tip numărător n1 şi n2. Utilizând pointerii *n1 şi *n2

putem reprezenta grafic modul în care pointează aceste obiecte.

Page 40: Programare Orientata Pe Obiecte

39

n2->n1>

Pentru adresarea unei anumite funcţii membră a clasei s-a utilizat operatorul “.” n1. setare(); sau n2.avans(); Funcţia setare din clasa numarator poate fi definită astfel: void numarator ::setare(int cs, int cz, int cu) { this->cifra_sute=cs; this->cifra_zeci=cz; this->cifra_unitati=cu; } Operatorul this va puncta pe începutul structurii clasei ca în figura următoare:

Utilizând acest operator putem să ne referim la orice element al obiectului curent dar nu însă şi în interiorul unei funcţii. Notaţia *this va reprezenta referirea la întregul obiect.

2.19 Prevenirea redeclarării claselor Programele realizate până acum conţineau definiţiile claselor în acelaşi fişier cu

funcţia main(). O tehnică obişnuită în C++ este de a declara şi defini clasa într-un fişier antet

inclus cu o comandă de genul următor: #include <nume_fişier_antet>

cifra _sute cifra_zeci cifra_unităţi setare() avans() afisare()

cifra _sute cifra_zeci cifra_unităţi setare() avans() afisare()

cifra _sute cifra_zeci cifra_unităţi setare() avans() afisare()

this ->

Page 41: Programare Orientata Pe Obiecte

40

Apariţia în mai multe locuri a instrucţiunii #include referitoare la acelaşi fişier antet, nu ar fi sancţionată ca eroare, dar ar prelungii durata compilării.

Pentru evitarea unei astfel de situaţii este recomandabil utilizarea următoarei secvenţe de instrucţiuni de preprocesare:

#ifndef nume #define nume //declaraţia şi definiţia clasei #endif. Exemplu: fişerul antet counter.h #ifndef counter_h #define counter_h # include <iostream.h> class numarator //definitia clasei numarator { ......... ...............} #endif counter_h Această tehnică stă la baza modularizării programelor.

2.20 Tehnici de creare şi iniţializare a obiectelor În general se creează mai multe obiecte, ceea ce impune utilizarea optima a

spaţiului de memorie dinamică. Acest lucru poate fi realizat prin eliminarea acelor obiecte a căror existenţă nu mai este oportună. Crearea dinamică a obiectelor poate fi realizată cu ajutorul funcţiilor pentru alocare (malloc(), calloc(), realloc()) şi una pentru eliberare free() a spaţiului de memorie având prototipul în alloc.h

Să considerăm clasa următoare: class agent { char nume [20]; int volum _vanz; int nr_ore_lucrate; int stoc; long salar_sapt; } Cu ajutorul funcţiei malloc() obţinem adresa în memoria dinamică unde va fi

alocată o astfel de structură de date la momentul definirii unei variabile cu aspectul agent. Întrucât malloc() întoarce o adresă, vom declara variabila p astfel: char *p.

Putem defini : agent Agent; şi vom obţine spaţiile în memoria dinamică prin: p=malloc(sizeof (Agent)); Acest lucru este acceptat în C şi în C++. În C++ există operatorul new , care ne

permite să alocăm spaţiu pentru obiect la momentul execuţiei. agent *p;

Page 42: Programare Orientata Pe Obiecte

41

p = new agent; Există o diferenţă netă între operatorul new şi funcţia de alocare malloc().

Operatorul new determină în mod automat spaţiul necesar prin analizarea “numelui de tip “ ce îi urmează.

Adresa întoarsă în p ne permite să avem acces la membrii structurii. Cu funcţia malloc trebuie să recurgem în mod explicit la operatorul sizeof, având

ca argument Agent. În limbajul C putem scrie de exemplu: int *p; p= malloc(100*sizeof(it)); adică alocă un spaţiu de 100 de cuvinte de tip întreg. Acest lucru în C++ se poate scrie astfel: int *p; p = new int[100]; După operatorul new urmează un nume de tip, în cazul de faţă int, iar între

paranteze drepte, dimensiunea adică numărul elementelor de acel tip, care vor fi alocate din zona de memorie.

În C, eliberarea spaţiului alocat dinamic se efectuează prin intermediul funcţiei free(), iar în C++ putem utiliza operatorul delete.

delete p; respective free(p); (delete se utilizează dacă am folosit new) Exemplu: double *p=new double[12]; delete [4]p;

2.21 Elemente despre preprocesare Preprocesarea este faza care precede compilarea propriu-zisă a unui fişier sursă. Până acum s-au studiat directivele: # include şi #define. Directiva include poate fi utilizată în două forme: # include <nume_fişier.h> # include “ nume_fişier.h” În primul caz compilatorul va căuta în lista definită de utilizator ..../include. A

doua formă instruieşte compilatorul să caute fişierul antet conform regulilor proprii ale sistemului de operare, mai întâi căile <..........> şi apoi cele “ .......”.

Page 43: Programare Orientata Pe Obiecte

42

2.22 Directive de compilare condiţionată Una dintre directivele de compilare condiţionată este #ifndef simbol // urmată de #define simbol Acest cuplu de directive va fi folosit în numeroase exemple. Semnificaţia lui este

următoarea: dacă până în acel loc simbol nu a fost definit, va fi definit acum (cu #define). Dacă era deja definit, atunci se va ignora instrucţiunea #define. Deci se evită redefinirea.

Cuplul următor: #ifdef simbol # define simbol #endif Are efect exact contrar cazului de mai sus. Funcţia #define în C++ este utilizată şi pentru definirea unor macrodefiniţii cu

parametrii. Exemplu: # define PI 3.14 # define unghi(v) 180*arccos(v)/PI ua = (b*b + c*c -a*a)(2*b*c); a=unghi(ua); Directiva #undef anulează o definiţie anterioară a unui identificator cu # define. Sintaxa este: # undef nume_macrodef După această directivă, nume_macrodef este detaşat de semnificaţia asociată

anterior şi este posibilă redefinirea sa cu #define. #error –Această directivă determină complilatorul să oprească compilarea atunci

când o întâlneşte. Directiva este utilizată pentru depanare. Directivele de compilare condiţionată permit compilarea unor porţiuni selectate

din codul sursă. Sunt folosite de exemplu, când se doreşte obţinerea mai multor variante ale

aceluiaşi program. Sintaxa construcţiei condiţionale este: # if(exp1) sectiunea_1 #else sectiunea_2

Page 44: Programare Orientata Pe Obiecte

43

# endif sau #if(exp1) sectiune_1 #elif(exp2) sectiune_2 . . . #elif(expn) sectiune_n #else(exp) sectiune_n+1 #endif

2.23 Prevenirea redeclarării claselor Programele realizate până acum conţineau definiţiile claselor în acelaşi fişier cu

funcţia main(). O tehnică obişnuită în C++ este de a declara şi defini clasa într-un fişier antet

inclus cu o comandă de genul următor: #include <nume_fişier_antet> Apariţia în mai multe locuri a instrucţiunii #include referitoare la acelaşi fişier

antet, nu ar fi sancţionată ca eroare, dar ar prelungii durata compilării. Pentru evitarea unei astfel de situaţii este recomandabil utilizarea următoarei

secvenţe de instrucţiuni de preprocesare: #ifndef nume #define nume //declaraţia şi definiţia clasei #endif. Exemplu: fişerul antet counter.h #ifndef counter_h #define counter_h # include <iostream.h> class numarator //definitia clasei numarator { ......... ...............} #endif counter_h Această tehnică stă la baza modularizării programelor.

Page 45: Programare Orientata Pe Obiecte

44

3 PROGRAMAREA ÎN VISUAL C++ 3 Visual C++ a devenit mult, mult mai mult decât un simplu compilator. Sunt

incluse clasele fundamentale Microsoft (M.F.C.), care simplifică şi accelerează dezvoltarea aplicaţiilor Windows. Sunt incluse editoare sofisticate de resurse în scopul proiectării casetelor de dialog complexe, a meniurilor, a barelor de instrumente, a imaginilor şi a altor multe elemente ce compun aplicaţiile Windows actuale. Este oferit un excelent mediu de dezvoltare integrat, numit Developer Studio, care prezintă forme grafice ale structurii aplicaţiei pe măsură ce o dezvoltăm. Un instrument pentru depanare integrat perfect ne permite să inspectăm în detaliu din cadrul unui program aflat în execuţie. Acestea sunt dor câteva din numărul mare de facilităţi oferite de Visual C++ 6.0, care ne ajută să dezvoltăm aplicaţii rapide şi complete folosind cele mai recente tehnologii Windows.

3.1 Crearea unui proiect Visual C++ 6 este un instrument potrivit pentru dezvoltarea de programe cu

interfaţă grafică. Visual Studio a purtat numele de Developer Studio şi este posibil să se

întâlnească şi acronimul IDE (Integrated Deyelopment Environment - mediu integrat de dezvoltare), folosit uneori ca şi referire la Visual Studio.

Fereastra Microsoft Visual C++ este afişată la lansarea mediului Visual C++. Microsoft Visual C++ este numele dat interfeţei cu utilizatorul a lui Visual C++, interfaţă afişată în figura 3.1. Această interfaţă reprezintă suprafaţa de lucru.

3.2 Fereastra interfeţei cu utilizatorul Pentru a începe o nouă aplicaţie trebuie să se creeze mai întâi un proiect. Un

proiect este folosit pentru administrarea tuturor elementelor care compun un program

Page 46: Programare Orientata Pe Obiecte

45

Visual C++ şi care au ca rezultat o aplicaţie Windows. Pentru a crea un nou proiect se selectează comanda New a meniului File şi deschide caseta de dialog New va fi afişată ca în figura 3.2.

Figura 3.1 interfeţa cu utilizatorul înVisual C++ Figura 3.2 Caseta de dialog New Pentru crearea unui proiect mai întâi trebuie să se specifice tipul acestuia. Dacă

pagina Projects din caseta de dialog New, nu este deja selectata, se face clic pe aceasta. Aici este afişată o listă cu toate tipurile de proiecte care se pot crea. Pentru exemplul se selectează MFC AppWizard (exe) din lista cu tipurile de proiect. Selectarea acestei opţiuni înseamnă că proiectul va avea ca rezultat un program executabil Windows standard.

Page 47: Programare Orientata Pe Obiecte

46

Orice proiect are nevoie de un nume. Acest nume se specifică în caseta Project_Name din caseta de dialog New. Pentru exemplul nostru, se va tasta ExempluPO în caseta Project Name din cadrul casetei de dialog New.

Caseta Location este folosită pentru precizarea directorului în care vor fi plasate fişierele proiectului. În cazul exemplului de faţă nu este nevoie să se modifice această locaţie.

Calea afişată iniţial în caseta Location depinde de opţiunile exprimate la instalarea lui Visual C++. Pentru a modifica această locaţie, fie se editează calea explicită, fie se efectuează un clic pe butonul aflat în partea dreaptă a casetei Location. Locaţia implicită se bazează numele proiectului - în exemplul nostru, C:\Program Files\Microsoft Visual Studio\My Projects\ ExempluPO.

După stabilirea opţiunilor din cadrul casetei de dialog New, se efectuează un clic pe OK pentru a iniţia generarea proiectului. Acest proces va fi controlat de AppWizard. Scopul acestuia este să permită realizarea unui schelet de program care poate fi dezvoltat ulterior. Acest lucru este realizat permiţând să se selecteze tipul de program după care este folosită biblioteca MFC, pentru generarea fişierelor care vor forma împreună un proiect Visual Studio.

Caseta de dialog AppWizard (ilustrată în figura 3.3) oferă trei opţiuni pentru tipul de interfaţă a aplicaţiei. În cazul proiectului Exemplu PO se va utiliza o interfaţă de tip dialog. Se selectează butonul de opţiune Dialog Based. Se poate, de asemenea, să selectaţi limba care va fi folosită pentru resurse. Nu este necesar să se modifice conţinutul casetei combinate Language în cazul programului ExempluPO.

Figura 3.3 Caseta de dialog MFC AppWizard – Pas 1 Acum s-a precizat toate informaţiile necesare pentru ca AppWizard să poată crea

proiectul. Se efectuează un clic pe butonul Finish. Se va afişa caseta de dialog New Project Information, ilustrată în figura 3.4.

Page 48: Programare Orientata Pe Obiecte

47

AppWizard va prezenta această casetă de dialog pentru a confirma detaliile proiectului pe care este gata să-1 creeze. Se poate vedea aici numele claselor C++ din proiect şi numele fişierelor care vor fi create. În lista Features se poate vedea, de asemenea, funcţionalitatea figurată de AppWizard. Pentru a continua se efectuează un clic pe OK în cadrul casetei de dialog New Project Information.

Figura 3.4 Caseta de dialog New Project Information AppWizard şi-a îndeplinit sarcina şi în Visual Studio este deschis acum proiectul

nou creat - ExempluPO. În acest moment, deşi nu s-a scris nici o linie de cod sursă C++, se are la dispoziţie o aplicaţie Windows completă şi perfect funcţională.

Secţiunea afişată de către Visual Studio în partea stângă se numeşte secţiunea spaţiului de lucru. După ce proiectul a fost creat, secţiunea spaţiului de lucru oferă trei pagini: ClassView, ResourceView şi FileView.

Aceste pagini permit accesarea oricărei componente a proiectului. Se poate modifica dimensiunea secţiunii spaţiului de lucru şi a altor secţiuni care apar în Visual Studio prin efectuarea unui clic pe marginea secţiunii şi deplasarea mouse-ului în timp ce se ţine butonul apăsat.

AppWizard este utilizat exclusiv pentru crearea noilor proiecte. Nu se poate reveni la casetele de dialog cu opţiuni AppWizard în cadrul unui proiect existent. Dacă se descoperă că s-a făcut o alegere greşită şi se doreşte reluarea etapelor AppWizard, trebuie să se înlăture mai întâi proiectul existent. Pentru a înlătura un proiect, se şterge directorul acestuia. Spre exemplu, pentru a relua crearea proiectului ExempluOP, se şterge directorul C:\Program Files\Microsoft Visual Studio\MyProjects\ExempluPO.

Page 49: Programare Orientata Pe Obiecte

48

Figura 3.5 Fereastra ExempluPO Procesul de asamblare are ca rezultat fişierul executabil Windows corespunzător

proiectului. În cazul proiectului ExempluPO, acest fişier va fi numit ExempluPO.exe. Odată generat acest fişier, se poate rula din Visual Studio.

Se poate crea fie o versiune pentru depanare, fie o versiune finală a unui fişier executabil. În mod implicit este asamblată o versiune pentru depanare; se va folosi această opţiune implicită în toate exemplele dacă nu se propune explicit o altă variantă. Pentru programul ExempluPO nu este necesară modificarea configuraţiei. În mod implicit, Visual Studio va genera o aplicaţie care conţine informaţii pentru depanare. Aceste informaţii permit să se inspecteze codul executat şi să se verifice valorile variabilelor. Înserarea informaţiilor pentru depanare duce, însă, la o creştere a dimensiunii fişierului executabil şi la o scădere a performanţelor. Configuraţia pentru versiunea finală asamblează executabilul fără informaţii pentru depanare, fiind folosită de regulă atunci când aplicaţia este livrată către un client.

Executabilele Visual C++ sunt fişiere Windows EXE standard Fişierul ExempluPO.exe este similar cu orice alt fişier executabil Windows. Prin

urmare, el poate fi executat din Explorer, sau puteţi să creaţi o scurtătură la acesta şi să o plasaţi pe suprafaţa de lucru.

3.3 Efectuarea compilării şi a editării de legături Procesul de asamblare efectuează compilarea fişierelor C++ individuale dintr-un

proiect, după care rezultatele sunt legate împreună pentru a forma fişierul executabil.

Page 50: Programare Orientata Pe Obiecte

49

Pentru a asambla proiectul ExempluPO, se efectuează clic pe butonul Build sau se selectează Build ExempluPO.exe din meniul Build sau se apasă tasta F7.

ExempluPO.exe se află în subdirectorul /Debug din directorul /ExempluPO al proiectului. Acest subdirector conţine, de asemenea, fişierele obiect ale programelor din cadrul proiectului. Subdirectorul /Debug a fost creat din cauză că s-a ales o configuraţie pentru depanare, în timp ce o configuraţie pentru versiunea finală ar fi plasat fişierele într-un subdirector numit /Release. Pagina Build a secţiunii Output înfăţişează informaţii privind procesul de asamblare. În cazul în care codul sursă conţine erori, acestea sunt afişate în cadrul paginii Build, ilustrată în figura 3.6.Deoarece AppWizard a generat întreg codul, nu ar trebui să apară erori.

Figura 3.6 Asamblarea proiectului ExempluPO Pentru a rula aplicaţia, se efectuează clic pe butonul Execute sau selectează

Execute ExempluPO.exe din meniul Build sau se foloseşte combinaţia de taste Ctrl+F5. Fereastra principală a aplicaţiei va apărea pe ecran ca în figura 3.7.

Fereastra aplicaţiei conţine două butoane, OK şi Cancel, şi afişează un text. Există, totodată, o bară de titlu care afişează o pictogramă asociată, numele aplicaţiei şi un buton de închidere. Bara de titlu conţine şi un meniu de sistem şi poate fi utilizată pentru a deplasa fereastra pe ecran prin efectuarea unui clic asupra ei şi menţinerea butonului mouse-ului apăsat.

Putem afişa informaţiile despre aplicaţie dacă se efectuează clic pe imaginea MFC din colţul stânga sus al ferestrei ExempluPO şi se selectează About ExempluPO din meniul de sistem care apare (figura 3.8).

Page 51: Programare Orientata Pe Obiecte

50

Figura 3.7 Aplicaţia ExempluPO Figura 3.8 Caseta de dialog About ExempluPO

3.4 Modificarea interfeţei aplicaţiei Elementele vizuale ale unui proiect se numesc resurse. Spre exemplu, casetele de

dialog, pictogramele şi meniurile constituie resurse. Numit sugestiv editor de resurse, acesta este instrumentul din Visual Studio care se foloseşte pentru proiectarea resurselor de diferite tipuri şi modificarea aspectului efectiv al aplicaţiei.

Pentru a adăuga un nou buton, va trebui să se deschidă macheta casetei de dialog.

Pentru aceasta se parcurg următorii paşi: 1. Se selectează pagina ResourceView a secţiunii spaţiului de lucru al

proiectului. Se va afişa lista cu resursele proiectului. 2, Se expandează lista de resurse prin efectuarea unui clic pe semnul + din

stânga etichetei ExempluPO Resources şi se expandează catalogul Dialog. Aşa cum se vede în figura 3.9, apar doi identificatori de dialog, IDD_ABOUTBOX ŞI IDD_EXEMPLUPO_DIALOG.

Page 52: Programare Orientata Pe Obiecte

51

Figura 3.9 Identificatorii de dialog din pagina ResourceView 3. Se efectuează un dublu clic pe identificatorul IDD_EXEMPLUPO_DIALOG.

În consecinţă va fi afişată macheta ferestrei dialog principale a aplicaţiei ExempluPO, ca în figura 310.

Figura 3.10 Editare idd_ExempluPO_dialog

Page 53: Programare Orientata Pe Obiecte

52

La rularea programului, dialogul va apărea aşa cum apare în editorul de resurse.

Acum se poate modifica macheta dialogului prin intermediul editorului de resurse. Pentru adăugarea unui buton în cadrul dialogului ExempluPO se parcurg

următorii paşi: 1. Înainte de a adăuga butonul, se înlătură controlul etichetă TODO care apare în

centul machetei dialogului ExempluPO. Se efectuează un clic pe textul TODO: place dialog controls here; în jurul textului este afişat un dreptunghi de formatare (figura 3.11). Se apasă tasta Delete. Controlul etichetă este înlăturat de pe macheta dialogului.

Figura 3.11 Ştergerea textului implicit din caseta de dialog 2. Se selectează controlul buton din caseta cu controale, aşa cum se vede în

figura 3.12. Se deplasează mouse-ul deasupra machetei dialogului. Aflat pe suprafaţa machetei, cursorul mouse-ului devine o cruce pentru a indica poziţia în care va fi plasat noul buton. Se plasează pointerul (crucea) departe de butoanele OK şi Cancel şi apoi se efectuează un clic cu mouse-ul. Astfel se va afişa un nou buton, etichetat Buttonl, ilustrat în figura 3.13.

Page 54: Programare Orientata Pe Obiecte

53

Figura 3.12 Selectarea controlului de tip buton din caseta de controale Figura 3.13 Adăugarea unui buton în cadrul casetei de dialog

Page 55: Programare Orientata Pe Obiecte

54

3. Pentru, a modifica eticheta butonului, trebuie ca acesta să fie selectat (încadrat

de un dreptunghi de formatare), ca în figura 3.11. Dacă butonul nu este selectat, se selectează prin efectuarea unui clic pe suprafaţa sa. Tastaţi Confirma. Odată ce tastaţi, este afişată caseta de dialog Push Button Properties, înfăţişată în figura 1.14.

4. Se tastează IDC_CONFIRMA în cadrul casetei combinate ID din caseta de dialog Push Button Properties, înlocuind textul implicit IDC_BUTTON1. Astfel, identificatorul butonului are o semnificaţie mai evidentă.

Figura 3.14 Caseta de dialog Push Button Properties 5. După închiderea casetei de dialog Push Button Properties, eticheta noului

buton din cadrul dialogului idd_ExempluPO__dialog va fi acum CONFIRMA.

3.5 Asocierea de cod cu interfaţa Pentru a fi utilizat butonul trebuie să se scrie secvenţa de cod care de fapt constă

decât într-o singură linie. Pentru a asocia codul necesar unui buton se parcurg următorii paşi:

1. Se deschide dialogul ExempluPO în cadrul editorului de resurse. 2. Se efectuează un clic cu butonul drept (de la mouse) pe butonul Confirma de pe

suprafaţa dialogului, după care se selectează opţiunea Events din meniul contextual afişat. Se va deschide caseta de dialog New Windows Message and Event Handlers corespunzătoare clasei dialog (figura 3.15). La efectuarea unui clic asupra butonului Confirma (în timpul rulării aplicaţiei), Windows trimite dialogului ExempluPO un mesaj. Caseta de dialog New Windows Message and Event Handlers va permite să se intercepteze acest mesaj şi să executaţi o - numită secvenţă de cod care să răspundă la eveniment.

3. Se va selecta BN_CLICKED din lista New Windows Messages/Events. Se observă că primul element din listă este selectat în mod implicit.

4. Se efectuează un clic pe butonul Add and Edit. Se va afişa caseta de dialog Add Member Function, ilustrată în figura 3.16. Aici se va da un nume funcţiei din program care va fi apelată de fiecare dată când caseta de dialog primeşte mesajul BN_CLICKED pentru butonul CONFIRMA.

Page 56: Programare Orientata Pe Obiecte

55

Figura 3.15 Adăugare de cod pentru evenimentul de acţionare a butonului

Figura 3.16 Denumirea funcţiei apelată de eveniment

Page 57: Programare Orientata Pe Obiecte

56

5 Se efectuează un clic pe OK pentru a accepta numele implicit OnConfirma. Corpul noii funcţii apare în fereastra editorului, ca în figura 3.17.

Figura 3.17 Codul funcţiei membru OnConfirma Funcţia este adăugată ca şi membru al clasei CExempluPODlg. Această clasă a

fost creată şi denumită în mod automat la crearea proiectului de către AppWizard. Funcţia

OnConfirma poate fi completată cu următorul cod void CExempluPODlg::OnConfirma() { // TODO: Add your control notification handler code here MessageBox(“Multumesc de ajutor!”); } Prin folosirea funcţiei predefinite MessageBox (), oricând se efectuează un clic

pe butonul Confirma va fi afişat într-o fereastră mică mesajul “Multumesc de ajutor!”. Atunci când se aduce modificări unui proiect şi dorim să vedem dacă acestea

funcţionează corect, va trebui să se repete procesul de asamblare şi rulare. Se efectuează un clic pe butonul Build sau se selectează Build ExempluPO.exe din meniul Build sau

Page 58: Programare Orientata Pe Obiecte

57

se apasă F7. Astfel se va relua asamblarea fişierului executabil, fiind incluse toate modificările aduse resurselor sau codului.

Modificările sunt salvate automat la asamblarea proiectului Visual Studio salvează automat orice fişier modificat înainte de a începe

asamblarea proiectului, astfel că nu mai trebuie să se salveze fiecare fişier editat. Dacă în timpul compilării apar erori, acestea vor fi afişate în pagina Build a

secţiunii Output aşa. cum se vede în partea inferioară a ferestrei Visual Studio ilustrată în figura 3.18.

Figura 3.18 Erorile sunt afişate în pagina build În continuare se efectuează un clic pe butonul Execute sau se selectează Execute

ExempluPO.exe din meniul Build sau se apasă CTRL+F5. Dialogul ExempluPO ar trebui să fie afişat ca în figura 3.19.

Dacă se efectuează un clic pe butonul Confirma va apare o fereastră de mesaj, similară cu cea din figura 3.20, care afişează mesajul Multumesc de ajutor!.

Dacă se efectuează un nou clic asupra butonului Confirma, se va primi acelaşi

mesaj. Aceasta se întâmplă deoarece la fiecare clic asupra butonului programul accesează aceeaşi funcţie OnConfirma ().

Page 59: Programare Orientata Pe Obiecte

58

Figura 3.19 Execuţia aplicaţiei ExempluPO Figura 3.20 Afişarea mesajului Dacă se efectuează un nou clic asupra butonului Confirma, se va primi acelaşi

mesaj. Aceasta se întâmplă deoarece la fiecare clic asupra butonului programul accesează aceeaşi funcţie OnConfirma ().

3.6 Salvarea şi închiderea proiectului Se poate salva un fişier anume în orice moment prin efectuarea unui clic pe

butonul Save . Este posibil, în plus, să se salveze toate fişierele specificate prin

efectuarea unui clic pe butonul Save All . Salvarea unui proiect nu necesită nici o operaţie. Odată ce s-a terminat lucrul cu

un proiect, este suficient să se închidă. Pentru a închide un proiect se selectează Close Workspace din meniul File sau pur şi simplu se închide Visual Studio.

Page 60: Programare Orientata Pe Obiecte

59

3.7 Utilizarea mediului Deweloper Studio Mediul Developer Studio utilizat de Visual C++ pare destul de complex la prima

vedere. O numărătoare rapidă arată că există peste 100 de opţiuni de meniu şi aproximativ tot atâtea butoane de pe bara cu instrumente care pot fi selectate. Multe dintre acestea conduc la casete de dialog complexe şi la pagini de proprietăţi care conţin numeroase opţiuni. Funcţionalitatea atât de bogată oferită de Developer Studio este justificată de faptul că acest mediu este utilizat pe scară largă pentru a produce aplicaţii complexe, profesionale.

3.8 Personalizarea mediului Developer Studio

Există multe modalităţi prin care mediul Developer Studio poate fi personalizat.

Opţiunile de meniu şi barele cu instrumente pot fi personalizate prin selectarea opţiunii Customize din meniul Tools. Se pot personaliza, de asemenea, fonturile şi culorile din ferestrele de editare, precum şi alte elemente, prin intermediul comenzii Options din meniul Tools. I

3.9 Deschiderea unui proiect existent Cea mai rapidă modalitate de a redeschide un proiect este să se apeleze lista

Recent Workspaces. În acest scop, se efectuează un clic pe meniul File; apoi se selectează proiectul dorit din cadrul listei Recent Workspaces. Pentru a deschide un proiect care nu figurează în lista Recent Workspaces, se selectează Open Workspace din meniul File. Pe ecran este afişată fereastra Open Workspace, înfăţişată în figura 3.21.

Se accesează directorul folosit pentru proiectul ExempluPO. Sel selectează din

listă fişierul ExempluPO.dsw şi se efectuează un clic pe Open. Indiferent de metoda utilizată, proiectul ar trebui să fie acum deschis, cu titlul

său afişat în bara de titlu a ferestrei Developer Studio. Mediul de lucru va deschide automat ultimul fişier sursă editat (figura 3.22).

Page 61: Programare Orientata Pe Obiecte

60

Figura 3.21 Deschiderea unui proiect existent prin selectarea fişierului .dsw Figura 3.22 Aplicatia ExempluPO

Page 62: Programare Orientata Pe Obiecte

61

Pentru a înlătura fereastra Output care apare în partea inferioară a ecranului, efectuaţi un clic pe cruciuliţa din colţul stânga sus al ferestrei.

Un spaţiu de lucru poate să conţină mai multe proiecte. 3.10 Fereastra spaţiului de lucru ai proiectului Fereastra spaţiului de lucra al proiectului este o fereastră andocabilă. Există mai

multe astfel de ferestre (fereastra Output este una dintre ele), unele apărând în modul depanare. Pentru a comuta între modul liber şi modul andocabil pentru o fereastră, se efectuează un dublu clic pe bara de titlu a respectivei ferestre. Fereastra spaţiului de lucru al proiectului poate fi închisă prin efectuarea unui clic pe micul buton de închidere aflat în colţul dreapta sus. Redeschiderea ferestrei se realizează prin selectarea comenzii Workspace din meniul View. Toate ferestrele andocabile au comportamente similare.

Fereastra spaţiului de lucru al proiectului permite vizualizarea proiectul din mai multe perspective. Atunci când este deschis un proiect, în partea inferioară a ferestrei spaţiului de lucru sunt disponibile trei pagini: ClassView, ResourceView şi File View. În cazul proiectelor dezvoltate pentru aplicaţii de baze de date este posibil să apară în plus o pagină DataView.

Paginile din partea inferioară a ferestrei împart proiectul în componente logice. Se va efectua un clic pe o pagină pentru a vizualiza reprezentarea corespunzătoare. Fiecare reprezentare afişează o structură arborescentă de elemente care reprezintă componente ale proiectului. Se poate expanda sau condensa elementele arborelui efectuând clic pe simbolul plus (+) sau minus (-) alăturat în partea stângă a acestora.

3.11 Lucrul cu reprezentarea claselor Dacă se efectuează un clic pe eticheta de pagină ClassView va fi afişată pagina

ClassView (reprezentarea claselor), ilustrată în figura 3.23. Această reprezentare afişează toate clasele folosite în cadrul proiectului.

AppWizard a creat aceste clase odată cu crearea iniţială a proiectului. Fiecare element aflat la nivelul secundar (mai puţin elementul Global) reprezintă o clasă şi afişează numele respectivei clase. La expandarea unei clase, fiecare element subordonat reprezintă fie o funcţie tu, fie o variabilă membru a clasei. De exemplu, în cadrul CExempluPODlg se poate vedea funcţia membru OnConfirma().

Pagina ClassView se dovedeşte de multe ori a fi cea mai utilizată. De aici se poate accesa orice clasă, orice funcţie membru sau orice variabilă din proiect. Efectuarea unui dublu clic asupra unui element va deschide fişierul cu cod sursă corespunzător în fereastra editorului, având cursorul plasat pe linia corespunzătoare elementului în cauză.

Page 63: Programare Orientata Pe Obiecte

62

Figura 3.23 Selectarea paginii ClassView Efectuarea unui dublu clic pe numele unei clase accesează definiţia clasei.

Efectuarea unui dublu clic pe o variabilă membru accesează definiţia variabilei. Efectuarea unui dublu clic pe o funcţie membru accesează începutul definiţiei funcţiei din cadrul fişierului care implementează clasa.

Pe lângă posibilitatea de a naviga printre clasele din proiect, secţiunea

ClassView oferă şi alte facilităţi prin intermediul meniurilor contextuale. Un meniu contextual este afişat pe ecran lângă cursorul mouse-ului la efectuarea unui clic cu butonul drept asupra unui element. Pentru a afişa meniul contextual pentru un element din arborele de clase, se selectează respectivul element cu butonul drept al mouse-ului. Elementul în cauză va ii evidenţiat şi alături de el va fi afişat meniul de context. Meniul contextual afişat depinde de tipul elementului selectat.

Dacă efectuăm un clic pe numele clasei CExempluPODlg se va afişa meniul contextual al clasei (figura 3.24)

Page 64: Programare Orientata Pe Obiecte

63

Figura 3.24 Afişarea meniului contextual Meniul contextual permite efectuarea unor operaţii diverse asupra clasei

selectate. Selectarea opţiunii Go to Definition are acelaşi efect cu efectuarea unui dublu clic pe numele clasei, şi anume accesarea fişierului sursă care conţine definiţia clasei. Patru dintre opţiunile meniului sunt folosite pentru adăugarea de funcţii sau variabile în cadrul clasei.

Dacă se efectuează un clic pe opţiunea References, pe ecran va apărea mesajul

din figura 3.25 Figura 3.25

Page 65: Programare Orientata Pe Obiecte

64

Dacă se efectuează un clic pe Yes, se va genera fişierul de navigare. Proiectul va fi asamblat automat. Odată încheiat procesul, se va închide fereastra Output prin efectuarea unui clic pe cruciuliţa aflată în colţul din stânga sus.

Informaţiile de navigare aduc facilităţi suplimentare de navigare, disponibile prin intermediul meniului contextual din ClassView.

Opţiunea References permite afişarea tuturor locaţiilor din proiect care fac referire la elementul selectat. Pentru fiecare referinţă sunt afişate numele fişierului sursă şi numărul ei, oferind posibilitatea de a accesa direct oricare dintre linii. Opţiunea Derived Classes afişează detalii privind clasa selectată şi clasele derivate din aceasta. Opţiunea Base Classes afişează detalii privind clasa selectată şi toate clasele sale de bază, inclusiv clasele din biblioteca MFC ( figura 3.26).

Figura 3.26 Activarea opţiunii Base Classes Atunci când se apelează meniul contextual pentru funcţiile sau variabilele

membru, opţiunea Calls afişează toate funcţiile apelate de către funcţia membru selectată, iar opţiunea Called By afişează toate locaţiile din proiect care conţin un apel al funcţiei.

Opţiunea Group By Access din meniul contextual este folosită pentru a ordona membrii clasei. Aceştia sunt afişaţi în ordine alfabetică în cazul în care opţiunea Group By Access nu este bifată; în caz contrar, membrii sunt afişaţi în ordinea tipului de acces - privat, protejat şi apoi public.

Pentru a prezenta avantajele meniului contextual vom dezvolta aplicaţia ExempluPO, permiţând ieşirea din program numai dacă butonul Confirma a fost acţionat cel puţin odată. Înainte de părăsirea aplicaţiei se va afişa de câte ori a fost acţionat butonul Confirma.

Se va adăuga o variabilă membru activând meniul contextual pentru clasa CExempluPODlg şi selectând Add Member Variable. Va fi afişată caseta de dialog Add Member Variable, prezentată în figura 3.27.

Page 66: Programare Orientata Pe Obiecte

65

Figura 3.27 Adăugarea unei noi variabile Va trebui să se specifice tipul, numele şi modul de acces pentru noua variabilă. Se va tasta int în caseta Variable Type şi i m__numara în caseta Variabile

Declaration. Se va lăsa modul de acces să fie Public. Astfel se creează în cadrul clasei CExempluPODlg o variabilă de tip întreg numită m__numara. Această variabilă va fi utilizată pentru a reţine de câte ori s-a efectuat clic asupra butonului Confirma. După introducerea informaţiile necesare, se efectuează un clic pe OK.

Noua variabilă ar trebui să fie acum afişată în pagina ClassView ca element

subordonat clasei CExempluPODlg. În continuare se va efectua un dublu clic pe elementul m_numara pentru deschiderea fereastrei editorului, cursorul fiind plasat în linia de cod care a fost adăugată pentru definirea noii variabile.

Deoarece această variabilă va reţine de câte ori s-a efectuat clic asupra butonului Confirma, ea va trebui să aibă valoarea iniţială zero. Locul potrivit pentru iniţializarea variabilei este constructorul clasei CExempluPODlg. Pentru a adăuga codul de iniţializare, se efectuează un clic pe elementul corespunzător funcţiei membru constructor CExempluPODlg (acesta fiind primul element subordonat clasei CExempluPODlg). Fereastra editorului va conţine fişierul sursă corespunzător. Pentru iniţializarea variabilei contor, se va adăuga prezentată în continuare:

CExempluPODlg::CExempluPODlg(CWnd* pParent /*=NULL*/) : CDialog(CExempluPODlg::IDD, pParent) { //{{AFX_DATA_INIT(CExempluPODlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in

Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_numara=0; }

Page 67: Programare Orientata Pe Obiecte

66

Variabila m_nClickCount este iniţializată cu zero în constructorul clasei casetă de dialog. Acum numărătoarea începe de la zero; mai departe va trebui să realizăm incrementarea contorului la fiecare clic asupra butonului Confirma. Se efectuează un dublu clic pe funcţia membru OnConfirma () a clasei CExempluPODlg, vizualizând codul acesteia.

Pentru a se incrementa variabila contor, se adauga m_numara++ coform

secvenţei de cod următoare: void CExempluPODlg::OnConfirma() { // TODO: Add your control notification handler code here MessageBox("Multumesc de ajutor!"); m_numara++; } OnConfirma() este o funcţie pentru tratarea mesajelor care este apelată de fiecare

dată când se efectuează un clic pe butonul lDC_CONFIRMA. Pe lângă afişarea unui mesaj pentru utilizator, funcţia incrementează o variabilă

contor pentru a număra de câte ori a fost apăsat butonul. Variabila contor va reţine numărul de clicuri. Următorul pas constă în crearea

unei funcţii logice care va trata situaţiile în care s-a făcut sau nu clic pe butonul Confirma. Pentru aceasta se efectuează un clic cu butonul drept pe numele clasei CExempluPODlg pentru apela meniul contextual. Se selectează Add Member Function şi va fi afişată caseta de dialog Add Member Function ( figura 3.28). Această casetă de dialog este folosită pentru adăugarea unei funcţii membru într-o clasă.

Figura 3.28 Adăugarea unei funcţii membru într-o clasă Se va specifica tipul funcţiei BOOL în caseta Function Type şi numele funcţiei

Clicnumara în caseta Function Declaration. Dacă noua funcţie necesită parametri, aceştia pot fi introduşi de asemenea în caseta Function Declaration.

De asemenea ar trebui ca butonul de opţiune care stabileşte modul de acces Public să fie selectat, iar casetele de validare Static şi Virtual trebuie lăsate ambele invalidate. După terminarea operaţiilor se efectuează clic pe OK. Noua funcţie ar trebui

Page 68: Programare Orientata Pe Obiecte

67

să apară acum în secţiunea ClassView ca şi element subordonat clasei CExempluPODlg. Corpul acestei funcţii este vizibil în fereastra editorului. În continuare se adaugă liniile de cod.

BOOL CExempluPODlg::clicnumara() { if (m_numara==0) { MessageBox("Nu ai confirmat", "Nu poti termina", MB_ICONSTOP); return FALSE; } if(m_numara>1) { CString str; str.Format("Ai confirmat de %d ori", m_numara); MessageBox(str); } else { MessageBox("S-a confirmat odata"); } return TRUE; } Funcţia realizată clicnumara este gata, dar încă nu este folosită nicăieri în

program. Pentru a integra funcţia se va deschide pagina ResourceView de la baza ferestrei spaţiului de lucra a proiectului, ce fişează o listă cu resursele din cadrul proiectului (figura 3.29).

Figura 3.29 Selectarea pagini Resource View

Page 69: Programare Orientata Pe Obiecte

68

Această reprezentare afişează toate resursele folosite în proiect. O Funcţia MessageBox () are mai multe forme. În acest caz este transmis

indicatorul mb_iconstop, ceea ce duce la afişarea unui semn de stop alături de textul mesajului.

Funcţia Format () a clasei Cstring este utilizată pentru a compune un mesaj textual care este transmis apoi funcţiei MessageBox ().

3.12 Resurse Visual C++ Noţiunea de resursă cuprinde elementele vizuale ale unui proiect. De pildă,

casetele de dialog sunt resurse, la fel ca şi meniurile sau pictogramele. Asemeni reprezentării claselor, reprezentarea resurselor afişează un meniu

contextual la efectuarea unui clic cu butonul drept asupra unui element. Se va selcta Insert din meniul contextual ExempluPO Resorces. Pe ecran va apărea caseta de dialog Insert Resource, ca în figura 3.30.

Figura 3.30 Caseta de dialog Insert Resources Fereastra spaţiului de lucru al proiecrtului este o fereastră andocabilă. Există mai

multe tipuri de astfel de ferestre, unele apărând în modul depanare Pentru a comuta între modul liber şi modul andocabil pentru o fereastră, se va efectua un dublu click pe bara de titlu a respectivei ferestre. Fereastra spaţiului de lucru al proiectului poate fi închisă prin efectuarea unui click pe micul buton de închidere din colţul dreapta sus.

Fereastra spaţiului de lucru al proiectului ne permite să vizualizăm proiectul din mai multe perspective. Atunci când este deschis un proiect în partea inferioară a ferestrei spaţiului de lucru sunt disponibile obligatoriu paginile: ClassView ResourceView, FileView.

Page 70: Programare Orientata Pe Obiecte

69

Secţiunea ClassView conţine structura de clase ale proiectului iar prin acţionarea pătratului din faţa numelui unei clase, semnul + se transformă în – şi are loc expandarea nodului respectiv fiind afişat un nivel ce descinde din nodul ce conţine numele clasei şi care conţine toate prototipurile funcţiilor membre ale clasei ca şi variabilele membre.

Fişierele dintr-un proiect VisualC++ se împart în : - Fişiere header - sunt fişiere care conţin partea de implementare a clasei. În

interiorul clasei se vor afla doar declaraţiile metodelor(funcţii membre ale clasei) şi de asemenea declaraţii ale variabilelor membre ale clarei. Acestea mai pot conţine şi implementarea unora dintre funcţiile membru, o astfel de implementare mai poartă numele de implementare inline a metodelor. Aceste metode vor funcţiona în momentul când vor fi apelare ca nişte macrouri, adică vor expanda şi nu vor crea propria stivă în memorie.

- Fişiere sursă - sunt fişiere care includ fişierele header şi conţin definiţia metodelor declarate în fişierele header.

- Fişiere resursă - sunt fişiere ce conţin resurse ale proiectului ex:imagini. Secţiunea FileView conţine lista tuturor fişierelor aplicaţiei. Acestea sunt afişate

sub forma unui arbore cu patru noduri care se desprind din rădăcină. Primul dintre acestea, Header Files, conţine fişierele header ale proiectului şi prin expandarea sa va fi afişat încă un nivel ce conţine toate fişierele de acest tip. Fişierele sursă se vor găsi in nodul cu numele Source File iar fişierele de resurse se vor găsi în nodul Resource File. Mai există un nod a cărui expandare va duce la afişarea tuturor fişierelor externe ca noduri fii ale acestuia şi care are numele External Dependencies.

Secţiunea ResourceView conţine lista cu resursele folosite în cadrul proiectului. Resursa reprezintă un element vizual al unui proiect. De pildă casetele de dialog

ca şi meniurile sau pictogramele sunt resurse. Motivul pentru care resurse de genul meniurilor, al tabelelor de şiruri şi al

casetelor de dialog sunt despărţite de codul sursă este acela că se doreşte posibilitatea de modificare a acestora independent de acesta. Astfel se înlesneşte foarte mult crearea de aplicaţii internaţionale. Spre exemplu, toate şirurile dintr-o tabelă de şiruri pot fi traduse într-o a doua limbă, fără a fi modificat codul sursă.

3.13 Tipuri de resurse Se pot vedea în această casetă diferitele tipuri de resurse care pot fi utilizate într-

un proiect Visual C++. O tabelă de acceleratori reprezintă o listă de asocieri între combinaţii de taste şi

comenzi din program. O denumire uzuală pentru accelerator este cea de combinaţie de taste. O combinaţie de taste reprezintă o modalitate mai rapidă de a executa o comanda decât selectarea acesteia dintr-un meniu.

O resursă de tip bitmap este o imagine de o dimensiune oarecare compusă din puncte. Fiecare punct poate avea o culoare distinctă. Bitmap-urile sunt utilizate pentru afişarea imaginilor care apar pe barele cu instrumente.

Un cursor este o resursă de tip imagine. Cursoarele sunt folosite în principal ca reprezentări ale indicatorului mouse-ului (cursorul standard din Windows este o

Page 71: Programare Orientata Pe Obiecte

70

săgeata). Trăsătura distinctivă a unui cursor este prezenţa unui punct senzitiv, care este folosit pentru a urmări poziţia acestuia pe ecran.

O pictogramă este, de asemenea, o resursă de tip imagine. Proiectul ExempluPO conţine o pictogramă care se numeşte IDR_MAINFRAME. Dacă se efectuează un dublu clic pe elementul IDR_MAINFRAME în fereastra editorului va fi deschisă pictogramă MFC standard.

O resursă de tip dialog este o machetă de fereastră care poate conţine celelalte tipuri de resurse, aşa cum sunt meniurile şi butoanele.

O resursă de tip meniu conţine exact ceea ce sugerează numele său, şi anume un meniu. De regulă, o aplicaţie include un meniu în partea superioară a ferestrei sale principale (aşa cum este si cazul cu Developer Studio), însă pot exista mai multe meniuri. De exemplu este posibilă adăugarea într-un proiect a unei resurse de tip meniu şi utilizarea acesteia ca şi meniu de context.

O resursă de tip tabelă de şiruri ar trebui să conţină toate şirurile de caractere folosite într-o aplicaţie. Fiecare element are asociat un identificator unic, utilizat pentru a referi şirul respectiv în cadrul codului sursă. Motivul pentru care toate şirurile sunt introduse într-o tabelă de şiruri şi nu direct în fişierele sursă este înlesnirea traducerii în alte limbi. Dacă toate textele utilizate într-o aplicaţie se află într-o tabelă de şiruri, este posibilă crearea unei a doua tabele intr-o altă limbă. După lansarea aplicaţiei este utilizată tabela de şiruri corespunzătoare.

O resursă de tip bară cu instrumente conţine o mulţime de butoane. De regulă, fiecare buton reprezintă o comandă de meniu. Pe suprafaţa butoanelor pot fi desenate imagini sub formă de bitmap-uri şi fiecare buton poate avea asociată o explicaţie care va fi afişată atunci când mouse-ul se deplasează deasupra butonului.

O resursă de tip versiune conţine informaţii despre aplicaţii, ca de pildă firma producătoare, numele produsului, un număr de versiune şi aşa mai departe. Aceste informaţii se află într-un format standard care permite accesarea lor din alte aplicaţii.

Resurse noi pot fi adăugate în orice moment. La creare, fiecare resursă primeşte automat un nume unic. De exemplu, o nouă machetă de dialog ar putea primi numele IDD_DIALOG1.

Figura 3.31 Caseta de dialog Resource Symbols

Page 72: Programare Orientata Pe Obiecte

71

Programatorii înlocuiesc de obicei numele generate automat cu nume de resurse mai sugestive pentru contextul programului, aşa cum s-a făcut când s-a locuit numele IDC_BUTTON1 cu IDC_CONFIRMĂ. Pentru a afişa numele tuturor resurselor din proiect, se va selcta Resource Syjmbols din meniul View. Va fi afişată caseta de dialog Resource Symbols (figura 3.31).

Se poate utiliza caseta de dialog Resource Symbols pentru a identifica o resursă anume pe numelui său simbolic prin selectarea numelui în cadrul listei şi efectuarea unui clic pe butonul View Use.

Fiecare resursă din cadrul proiectului poate fi editată prin efectuarea unui dublu clic pe numele acesteia din secţiunea ResourceView. Instrumentele şi opţiunile disponibile în fereastra editorului se modifică în concordanţă cu tipul resursei editate.

Prin intermediul secţiunii Resource View se parcurg paşii următori:

1. Se efectuează dublu clic pe elementul IDD_ExempluPO_DIALOG pentru a deschide macheta dialogului.

2. Se efectuează dublu clic pe butonul OK şi se afişează caseta de dialog Add Member Function. Se acceptă numele implicit al funcţiei membru OnOK. Se va afişa scheletul noii funcţii în fereastra editorului.

3. Se edita funcţia OnOk conform secvenţei de cod următoare void CExempluPODlg::OnOK() { // TODO: Add extra validation here if(clicnumara()==TRUE) { CDialog::OnOK(); } } void CExempluPODlg::OnCancel() { // TODO: Add extra cleanup here if (clicnumara()==TRUE) { CDialog::OnCancel(); } }

4. Se va efectua dublu clic pe elementul IDD_ExempluPO_DIALOG pentru a

deschide din nou macheta dialogului. 5. Se efectuează dublu clic pe butonul Cansel. 6. Se efectuează un clic pe OK pentru a acepta numele implicit al funcţiei membru:

OnCancel 7. Se editează funcţia implicită OnCancel conform secvenţei de cod următoare

Page 73: Programare Orientata Pe Obiecte

72

void CExempluPODlg::OnCancel() { // TODO: Add extra cleanup here if (clicnumara()==TRUE) { CDialog::OnCancel(); } } Acum aplicaţia poate fi asamblată şi rulată. Dacă se încearcă ieşirea din program fără confirmare este afişat mesajul următor: Figura 3.32 Dacă se apasă butonul confirmă este afişat mesajul următor. Figura 3.33 După ce este apăsat butonul confirmă se poate ieşi din program şi se afişează

numărul de confirmări. Figura 3.34

Page 74: Programare Orientata Pe Obiecte

73

3.14 Utilizarea controalelor În cadrul unei resurse de tipul casetǎ de dialog pot fi plasate o serie de

instrumente numite controale. Acestea au rolul de a facilita afişarea şi introducerea de date ca şi de a executa o comandǎ ca urmare a acţionǎrii asupra unora dintre ele. Dintre aceste controale menţionez: butoane de comandǎ, radio butoane, casete de opţiune, casete de editare, etichete, casete de grupare, controlul spin, bare de derulare, indicatorul de evoluţie, controlul imagine, caseta combinatǎ, glisorul, si altele.

Controalele se găsesc în caseta Controls. Aceastǎ casetǎ poate sǎ fie activatǎ sau

dezactivată din meniul Tools selectând opţiunea Customize… iar din caseta de dialog ce va fi afişatǎ, se va selecta secţiunea Toolbars din partea de sus a acesteia. Asiguraţi-vă acum cǎ este selectatǎ opţiunea Controls dacǎ doriţi afişarea casetei de instrumente Controls, sau cǎ nu este selectatǎ dacǎ doriţi dezactivarea acesteia.

33..1144..11 CCoonnttrrooaallee ddee ttiipp bbuuttoonn ddee ccoommaannddăă Butoanele sunt controalele utilizate cel mai frecvent. Ele se întâlnesc în aproape

toate casetele de dialog. Butonul de comandă este cel mai simplu control din Windows. Fiecare buton de comandă reprezintă o singură comandă şi, la acţionarea unui clic asupra sa, îndeplineşte acţiunea care execută acea comandă. Aproape toate casetele de dialog conţin cel puţin două butoane de comandă: Cancel şi OK.

Editorul de resurse adaugă în casetele de dialog nou create butoanele de comandă Cancel şi OK. Clasa MFC de bază pentru toate casetele de dialog este CDialog iar aceasta conţine implementări implicite care răspund la mesajele BN_CLICKED ale acestor butoane: OnOK() şi OnCancel(). Puteţi să supradefiniţi aceste funcţii în propriile clase dialog derivate din CDialog pentru a le ajusta comportmentul.

AppWizard creează automat aceste butoane pentru aplicaţiile de tip dialog. Multe casete de dialog implică şi alte butoane de comandă; acestea pot fi adăugate prin intermediul editorului de resurse. Proprietăţile şi opţiunile de stil iniţiale pentru fiecare buton sunt specificate tot prin intermediul editorului de resurse.

33..1144..22 CCoonnttrroolluull eettiicchheettăă ssttaattiiccăă Deseori se adaugă text cu rol de etichetă pentru controale sau pentru a descrie o

opţiune, sau pur şi simplu pentru a afişa o propoziţie. Atunci când într-o casetă de dialog este nevoie de afişarea unui text simplu

apelaţi la controlul etichetă statică. Fiecare control etichetă statică poate afişa până la 255 de caractere, introduse ca şi etichetă. Puteţi folosi caracterul (\n) linie nouă pentru a desfăşura textul pe mai multe linii, fiind posibilă alinierea la stânga, la dreapta sau pe

Page 75: Programare Orientata Pe Obiecte

74

centru. O serie de opţiuni de stil permit încadrarea controlului cu margini adâncite, ridicate sau de alte diverse tipuri.

33..1144..33 CCaasseettee ddee eeddiittaarree Casetele de editare permit introducerea de informaţii în timpul rulării

programului. Pentru acest lucru inseraţi o casetă de editare prin intermediul editorului de resurse din caseta de instrumente Controls. Din meniul contextual afişat la executarea unui clic dreapta pe acest control va fi selectată opţiunea ClassWizard…. Din caseta ce va fi afişată alegeţi secţiunea Member Variables şi asiguraţi-vă că în caseta derulantă Object IDs este selectat controlul corespunzător(numele casetei de editare). Acţionaţi apoi butonul Add Variable… iar în caseta da dialog ce se va deschide introduceţi câmpul Member variable name un nume pentru variabila ce o veţi asocia controlului (acesta va începe obligatoriu cu m_). Din caseta combinată Category alegeţi tipul noii variabile membru, acesta va fi CString dacă doriţi să introduceţi un şir de caractere, int pentru un număr întreg etc.

Această variabilă apare ca şi variabilă membru a clasei asociate casetei de dialog căreia îi aparţine controlul casetă de editare şi are rolul de a-şi însuşi valoarea introdusă de utilizator în caseta de editare, pentru ca aceasta să poată fi utilizată în program. Variabila mai poate fi folosită şi la setarea unei valori în caseta de editare.

Dacă se doreşte preluarea de informaţie, după ce utilizatorul a introdus o valoare în caseta de editare, vom apela funcţia UpdateData(). În acest moment variabila este setată pe valoarea introdusă, şi apelul acestei funcţii se va face, de exemplu la acţionarea unui buton.

Dacă se doreşte afişarea conţinutului variabilei în caseta de editare se setează mai întâi variabila pe valoarea ce va fi afişată iar mai apoi se apelează aceeaşi funcţie ca şi pentru operaţia inversă prezentată mai sus dat cu argumentul FALSE de maniera UpdateData(FALSE).

33..1144..44 CCoonnttrroolluull bbaarrăă ddee ddeerruullaarree

Barele de derulare se întâlnesc de multe ori ataşate de marginile unei ferestre în scopul derulării conţinutului acestora. Cu toate acestea ele se pot utiliza pentru a specifica o poziţie în cadrul unui interval specificat. Paleta Controls conţine două instrumente, unul asociat barelor de derulare orizontale şi unul barelor de derulare verticale.

Pagina Style a casetei de dialog ScrollBar Properties conţine o opţiune ce ne permite alinierea barei de derulare, opţiunea Align. Aceasta poate lua valorile:

None – desenează o bară de derulare având aceleaşi dimensiuni cu cele ale barei de derulare plasate pe macheta casetei de dialog.

Top/Left – va fi desenată o bară de derulare având lăţimea standard şi fiind aliniată la marginile din stânga şi sus ale barei derulante plasate pe caseta de dialog.

Page 76: Programare Orientata Pe Obiecte

75

Buttom/Right – Asemănător cu opţiunea anterioară doar că alinierea se va face la marginile din dreapta şi jos ale barei de derulare.

Maparea unei variabile peste un control de tip bară de derulare presupune

următorii paşi: - Se accesează meniul contextual pentru controlul bară de derulare printr-un clic

dreapta pe acest control; - Se apelează opţiunea ClassWizard… care va deschide caseta de dialog

corespunzătoare; - Vom accesa secţiunea Member Variables a acestei casete de dialog; - Ne asigurăm ca în secţiunea Control IDs este selectat numele resursei bară de

derulare; - Acţionăm butonul Add Variable… - În secţiunea Member variable name a casetei de dialog deschise vom

introduce numele noii variabile care va începe obligatoriu cu m_. - În secţiunea Category vom alege control - Acţionăm butoanele OK pentru toate casetele de dialog deschise.

Unei astfel de variabile asociate unui indicator de evoluţie îi vom asocia un

interval. Poziţia la care se află iteratorul de pe bara de derulare va reprezenta la un moment dat o valoare din intervalul setat.

Fixarea intervalului se va face cu funcţia SetScrollRange. Aceasta primeşte două valori pentru cele două capete ale intervalului si este identică pentru cele două tipuri se scrollbar-uri (orizontal şi vertical). Puteţi transmite orice valori întregi ca limite inferioară şi superioară pentru interval, dar diferenţa dintre aceste două limite nu trebuie să depăşească 32767.

Se poate stabili poziţia curentă a barei de derulare, ilustrată de caseta mobilă ce se află pe bară , cu ajutorul funcţiei SetScrollPos() şi transmiţând o valoare întreagă ce reprezintă poziţia în cadrul intervalului definit.

Există şi o funcţie inversă GetScrollPos() care întoarce valoarea poziţiei curente.

33..1144..55 CCoonnttrroolluull gglliissoorr Un control de tip glisor permite utilizatorului să stabilească o valoare prin

tragerea unui indicator de-a lungul unui interval liniar şi plasarea acestuia într-o anumită poziţie asemănător unui potenţiometru de volum al unui sistem audio.

Controlul glisor este un echivalent sofisticat al controlului bară de derulare prin aceea că are asociat de asemenea un interval ce poate fi definit, şi o poziţie curentă. Cu toate acestea, controlul glisor permite o personalizare şi o configurare mult mai complexe decât controlul bară de derulare.

Se poate plasa un control glisor pe suprafaţa unei casete de dialog după selectarea sa din cadrul paletei Controls, aşa cum s-a indicat şi în cazul altor controale. Se poate dimensiona şi poziţiona controlul după nevoie şi este bine să stabilească un identificator adecvat pentru control prin intermediul paginii General a casetei de dialog Slider Properties .

Page 77: Programare Orientata Pe Obiecte

76

În pagina Styles se află două casete combinate. Prima dintre ele este Orientation, care permite alegerea uneia din opţiunile Horizontal sau Vertical. Glisorul este implicit orizontal, dacă schimbaţi orientarea s-ar putea să fiţi nevoit să redimensionaţi fereastra controlului pentru a o adapta noului aspect. Cea de-a doua casetă combinată permite alegerea unui tip de cursor. În mod normal este selectat tipul Both, care are ca efect desenarea unei casete rectangulare pe post de indicator de poziţie. Pute înlocui tipul Both cu tipul Top/Left ceea ce va duce la afişarea unui indicator al cărui vârf este îndreptat spre partea superioară a unui glisor orizontal, respectiv către partea din stânga a unui glisor vertical. Se poate selecta ca alternativă tipul Buttom/Right pentru afişarea unui indicator al cărui vârf este îndreptat în partea opusă faţă de indicatorul Top/Left.

De asemenea mai pot fi selectate o serie de casete de validare: - Tick Marks. Dacă această opţiune este selectată glisorul va afişa linii mici

perpendiculare pe direcţia controlului şi situate în partea indicată de cursor, scopul lor este acela de a ajuta utilizatorul să aproximeze mai bine poziţia cursorului.

- Auto Ticks. Selectarea acestei opţiuni duce la plasarea diviziunilor de-a lungul intervalului astfel încât să corespundă valorii de increment curente.

- Enabled Selection. Dacǎ opţiunea este selectatǎ, este adăugată o bară albă care permite programului sǎ afişeze un interval de selecţie prin intermediul unor mici triunghiuri.

- Border Dacǎ se selectează aceastǎ opţiune, în jurul glisorului se va trasa un cadru negru subţire. Maparea unei variabile peste un control glisor se realizează aproape identic cu

maparea unei variabile peste o bară de derulare. O variabilă mapată peste un control de tip glisor va fi de tipul CSliderCtrl. Puteţi folosi o astfel de variabilă pentru activarea sau dezactivarea din program a

controlului respectiv. Aceasta se poate face apelând funcţia EnableWindow() a variabilei mapate, transmiţând TRUE sau FALSE pentru a activa, respectiv dezactiva fereastra. Acest lucru l-aţi putea dori pentru a împiedica utilizatorul să modifice starea controlului în anumite circumstanţe.

Intervalul unui control glisor poate fi stabilit prin apelul funcţiei SetRange() asociate, în apel transmiţându-de ca primi doi parametrii valorile întregi minimǎ şi maximǎ pentru poziţia glisorului. Al treilea parametru poate lua valoarea FALSE şi are ca efect prevenirea redesenării automate a cursorului. Acest parametru este cu caracter opţional. Există de asemenea funcţii care stabilesc limitele inferioară şi superioară ale intervalului, în acest scop transmiţându-se un întreg în apelul funcţiilor SetRangeMin() sau SetRangeMax(). Funcţiile reciproce GetRangeMin() şi GetRangeMax() întorc limitele curente ale intervalului de valori.

Acea suprafaţă a controlului glisor de-a lungul căreia se deplasează glisorul reprezintă canalul rectangular. Dimensiunile şi poziţia acestei suprafeţe rectangulare pot fi determinate apelând funcţia GetCannelRect() a clasei CSliderCtrl.

Poziţia cursorului poate fi stabilită prin apelul funcţiei SetPos(), transmiţându-se o valoare întreagă care reprezintă noua poziţie din cadrul intervalului, şi poate fi determinată prin valoarea întoarsă de apelul funcţiei membru GetPos().

Page 78: Programare Orientata Pe Obiecte

77

3.15 Lucrul cu imagini în Visual C++ Imaginile grafice domină mediul Windows. Lucrurile stau astfel din mai multe

motive. Imaginile vor fi înţelese de orice persoană indiferent de limba pe care o vorbeşte, ocupă mai puţin spaţiu decât echivalentele textule şi în plus arată bine. Principalul scop al utilizării imaginilor grafice este să ajute utilizatorul să recunoască un program sau o funcţie anume mai rapid decât din parcurgerea unui text descriptiv.

Aplicaţiile Windows folosesc mai multe tipuri de imagini grafice: - imagine pictogramă este asociată cu însăşi aplicaţia şi este afişată în Windows

Explorer. De asemenea pictograma unei aplicaţii este afişată de regulă pe suprafaţa de lucru pentru a indica o scurtătură.

- Imaginile bitmap sunt folosite în cadrul ecranelor introductive şi pe butoanele de pe barele de instrumente, putând fi plasate şi în casetele ce dialog sau în alte ferestre.

- Imaginile cursor sunt folosite pentru a modifica reprezentarea grafică a cursorului de mouse. Acestea se întâlnesc frecvent în programele de desenare, având rolul de a sugera cum poate fi editat sau deplasat un obiect selectat de pe ecran.

- Editorul de resurse din Developer Studio permite să se creeze şi să se editeze fiecare tip de imagine. Se poate adăuga într-un proiect pictograme, bitmap-uri şi cursoare câte se doresc.

33..1155..11 UUttiilliizzaarreeaa eeddiittoorruulluuii ddee iimmaaggiinnii Editorul de resurse de tip imagine permite crearea de imagini de tipul bitmap,

pictogramă, sau cursor. Se poate desena cu mâna liberă sau să se folosească diferite opţiuni şi instrumente care vă ajută la trasarea de figuri pline şi contururi. Fereastra editorului este în mod normal împărţită în două secţiuni, în stânga fiind afişată imaginea în mărime naturală, iar în dreapta fiind afişată în versiune mărită. Aceasta din urmă permite distingerea fiecărui pixel în parte în scopul unei editări cât mai precise. Ambele imagini sunt actualizate automat în timpul editării. Indiferent de tipul resursei editate, multe din funcţiile de editare grafică sunt identice. Meniul Image este disponibil oricând este activată o fereastră de editare a unei imagini. Acest meniu conţine mai multe instrucţiuni de desenare. Opţiunea Invert Colors oferă posibilitatea de a inversa culorile dintr-o zonă selectată a unui bitmap. În plus, o zonă selectată poate fi oglindită sau rotită. Tot prin intermediul meniului Image se poate defini şi salva propriile palete de culori Instrumentele de desenare se află în caseta cu instrumente Graphics, iar culorile se selectează din paleta Colors.

Page 79: Programare Orientata Pe Obiecte

78

33..1155..22 CCrreeaarreeaa şşii eeddiittaarreeaa rreessuurrsseelloorr ddee ttiipp ppiiccttooggrraammăă Din punct de vedere vizual distincţia între bitmap-uri şi pictograme este greu de

făcut, dar există o serie de deosebiri importante. Un bitmap este o secvenţă de date care reprezintă o imagine în culori formată din pixeli. O pictogramă se compune din două bitmap-uri; primul reprezintă o imagine în culori, iar cel de-al doilea reprezintă un bitmap mască. Îmbinarea informaţiilor conţinute în aceste două bitmap-uri face posibilă existenţa zonelor de transparenţă şi de inversare în cadrul pictogramelor.

Se pot întâlni pictograme care au aspecte neregulate – rotunde, de pildă. În realitate, toate pictogramele sunt dreptunghiulare; masca de transparenţă este cea care le permite să pară neregulate. O pictogramă poate conţine mai tulte imagini care diferă prin dimensiune şi număr de culori, fiind destinate unor dispozitive de afişare diferite.

Un proiect poate conţine oricâte resurse de tip pictogramă. Acestea pot fi create şi desenate de la zero sau pot fi importate dintr-un proiect existent sau un fişier cu extensia „.ico”. Pictogramele nou create sunt iniţial transparente şi sunt destinate implicit dispozitivelor VGA, având dimensiunea de 32x32.

33..1155..33 CCrreeaarreeaa şşii eeddiittaarreeaa rreessuurrsseelloorr ddee ttiipp bbiittmmaapp Resursele de tip bitmap au numeroase întrebuinţări. Pot fi utilizate pentru efecte

vizuale, sau pentru afişarea lor pe butoane de comandă ca alternativă la o etichetă. Spre deosebire de dimensiunile relativ limitate ale pictogramelor, o imagine

bitmap poate avea până la 2048x2048 de pixeli. Din nou spre deosebire de pictograme, bitmap-urile nu conţin atribute de culoare transparentă sau inversă. Un proiect poate conţine oricâte resurse de tip bitmap, acestea putând fi create de la zero sau importate dintr-un fişier cu extensia „.bmp”.

33..1155..44 IImmppoorrttaarreeaa iimmaaggiinniilloorr Se poate importa resurse din fişiere şi proiecte existente, sau chiar din fişiere

executabile aflate în sistem.

33..1155..55 IImmppoorrttuull iimmaaggiinniilloorr ddiinn ffiişşiieerree „„..iiccoo”” şşii „„..bbmmpp””

Pentru a importa o pictogramă sau un bitmap dintr-un fişier .ico sau .bmp, se vor parcurge următorii paşi.

- Se selează Import din meniul contextual al paginii Resource View, sau se selectează Resource din meniul Insert şi apoi se efectuează un click pe butonul

Page 80: Programare Orientata Pe Obiecte

79

Import din caseta de dialog Insert Resource. Va fi afişată caseta de dialog Insert Resource. Această casetă de dialog este o versiune puţin modificată a dialogului File Open.

- Se accesează directorul corespunzător şi se selectează fişierul conţinând resursa pe care se doreşte a fi importată, apoi se efectuează un click pe butonul Import Se poate utiliza caseta combinată File of Type pentru a determina afişarea exclusiv a unui anumit tip de fişiere resursă.

- Dacă fişierul selectat are un tip recunoscut, resursa va fi adăugată la proiect, va fi generat automat un identificator unic şi în directorul proiectului va fi plasată o copie a fişierului importat.

33..1155..66 IImmppoorrttaarreeaa rreessuurrsseelloorr ddiinn ffiişşiieerree eexxeeccuuttaabbiillee Pentru importarea resurselor din fişiere executabile se parcurg următorii paşi:

- Se selectează Open din meniul File. Va fi afişată caseta de dialog Open obişnuită.

- Se selectează Resource din caseta combinată Open As. - Se selectează directorul corespunzător şi în continuare fişierul executabil (.exe,

.dll sau .ocx) din care se doreşte să se importe; apoi se efectuează un clic pe butonul Open. Resursele încapsulate în fişierul executabil vor fi afişate în fereastra editorului. Ca exemplu, se selecteazâ fişierul Cards.dll, plasat în mod normal în directorul c:\\Windows\\System. La deschiderea fişierului din sistem este bine să se valideze opţiunea Open as Read-Only.

- Pentru vizualizarea resurselor, se deschide caseta de dialog cu proprietăţi şi se efectuează un clic pe pioneza afişată în colţul din stânga sus pentru a menţine caseta deschisă. Cadrul Prewiew va înfăţişa fiecare resursă selectată.

- Pentru a insera o resursă din lista afişată, se selectează cu mouse-ul, se ţine butonul mouse-ului apăsat împreună cu tasta Ctrl şi se va deplasa cursorul în interiorul paginii Resource View. Se eliberează butonul mouse-ului în momentul în care alături de cursorul său este afişat un semn plus. Resursa selectată va fi adăugată la proiect.

- Din meniul File se selectează Close.

33..1155..77 UUttiilliizzaarreeaa iimmaaggiinniilloorr îînn ccaasseetteellee ddee ddiiaalloogg Existǎ multe modalităţi de a afişa imagini în cadrul casetelor de dialog.

Pictogramele şi bitmap-urile sunt afişate cu uşurinţǎ într-o casetǎ de dialog, folosind un control imagine şi stabilindu-i proprietăţile pentru a afişa resursa imagine doritǎ. Controalele imagine pot fi folosite şi pentru crearea unor dreptunghiuri colorate sau a unor cadre în scopul de a grupa elementele dintr-o casetǎ de dialog.

În caseta de proprietăţi a controlului imagine, în secţiunea General avem

posibilitatea de a opta pentru unul din tipurile de imagine: 1. cadru - afişează un cadru alb, gri sau adâncit, folosit ca efect vizual pentru

gruparea elementelor.

Page 81: Programare Orientata Pe Obiecte

80

2. casetǎ – afişează o casetǎ albǎ, gri, sau tridimensionalǎ. 3. pictogramǎ – afişează o resursǎ imagine de tip pictogramǎ. 4. bitmap – afişează o resursǎ imagine de tip bitmap. 5. metafişier extins – afişează imaginea stocatǎ într-un metafişier extins.

Se poate opta pentru una din aceste variante prin selectarea sa din caseta derulantǎ a câmpului Type a casetei Picture Properties.

În secţiunea Image poate fi selectatǎ una din resursele de tip imagine disponibile, resursǎ ce va fi afişatǎ pe ecran în zona controlului imagine.

O imagine bitmap poate fi încărcată prin intermediul unui obiect CBitmap, apelând funcţia LoadBitmap a acestuia şi transmiţându-i identificatorul resursei. Bitmap-ul este apoi afişat în cadrul controlului de tip imagine prin apelarea metodei SetBitmap a acestui control, metoda va primi ca argument variabila de tip CBitmap.

Exemplu: . . . CBitmap m_bmp; VERIFY(m_bmp.LoadBitmap(IDB_SUMSET)); m_b1.SetBitmap(m_bmp); . . . Unde m_b1 este variabila de tip control asociatǎ unui control de tip imagine,

IDB_SUMSET este o resursǎ de tip imagine creatǎ în prealabil iar VERIFY a fost utilizatǎ pentru a verifica corectitudinea modului în care se executǎ LoadBitmap.

33..1155..88 CCrreeaarreeaa bbuuttooaanneelloorr ggrraaffiiccee Deoarece imaginile sunt adesea mai uşor de perceput decât cuvintele, uneori este

de preferat afişarea unei imagini pe suprafaţa unui buton în locul unei etichete textuale. Tocmai în acest scop, biblioteca MFC pune la dispoziţie clasaCBitmapButton.

Se foloseşte editorul de imagini pentru a crea bitmap-urile, iar butoanele de comandǎ sunt plasate într-o machetǎ de dialog în maniera cunoscutǎ. La crearea de resurse pentru butoanele grafice trebuie sǎ ţinem seama de douǎ lucruri. În primul rând, se va specifica un şir de caractere ca identificator al bitmap-ului, şi un numǎr care sǎ fie folosit apoi cu #define. În al doilea rând selectaţi proprietatea Owner Draw a controlului buton de comandǎ. Resursa Bitmap va fi asociatǎ cu butonul de comandǎ prin intermediul funcţiei CbitmapButton::AutoLoad.

Un buton de comandǎ poate avea patru stări (ridicat, apăsat, dezactivat sau selectat). Starea butonului se modificǎ atunci când utilizatorul efectuează un clic sau apasǎ tasta tab, dar se poate schimba şi prin cod. Butoanele de comandǎ normale, care conţin etichete textuale, se ocupǎ automat de modificarea aspectului odată cu starea. De exemplu eticheta poate fi afişatǎ adâncitǎ atunci când butonul este dezactivat.

De afişarea butoanelor grafice este responsabil cel care le controlează, rin urmare vom fi responsabili de furnizarea unei imagini pentru fiecare stare. În cazul în care se pune la dispoziţie o singurǎ imagine, utilizatorul nu va sesiza nici o schimbare

Page 82: Programare Orientata Pe Obiecte

81

atunci când, de pildǎ, efectuează un clic pe buton. Trebuie, aşadar, sǎ furnizaţi imagini măcar pentru stările ridicat şi apăsat. Identificatorul unui astfel de bitmap trebuie sǎ fie un şir de caractere (inclusiv ghilimelele) bazat pe eticheta butonului. De exemplu, dacǎ butonul are eticheta „SUMSET” identificatorii pentru cele patru imagini vor fi: „SUMSETU” (ridicat), „SUMSETD” (apăsat), „SUMSETX” (dezactivat) şi „SUMSETF” (selectat).

3.16 Lucrul cu fişiere în Visual C++

Serializarea este o tehnică folosită pentru transformarea datelor aplicaţiei într-o

listă secvenţială de elemente de date individuale, urmată de stocarea lor pe disc. Serializarea documentelor este foarte utilă în cadrul aplicaţiilor care trebuie să stocheze date din cadrul documentelor, dar sunt situaţii în care este necesară crearea de fişiere, scrierea în acestea, ca şi citirea lor în mod direct. Pe de altă parte este bine să folosim cât mai puţin facilităţile oferite de mediul de dezvoltare, pentru a putea să facem o eventuală trecere relativ uşoară pe un alt mediu ce nu oferă atâtea facilităţi.

MFC oferă o clasă de încapsulare pentru fişierele de pe disc, clasă numită CFile. Această clasă încorporează toate operaţiile cu fişiere şi toate atributele asociate unui fişier de pe disc. Prin crearea şi utilizarea obiectelor CFile putem să creăm, deschidem, citim şi scriem fişiere, apelând la funcţiile oferite de clasa CFile.

Indiferent dacă serializăm datele unui document sau scriem direct într-un fişier, vom fi nevoiţi să folosim până la urmă clasa CFile sau una din derivatele sale.

33..1166..11 CCrreeaarreeaa oobbiieecctteelloorr ddee ttiipp ffiişşiieerr Există trei moduri de a crea un obiect de tip CFile. Cel mai simplu mod de a crea

un obiect CFile este de a apela constructorul implicit fără a transmite nici un fel de parametru, obiectul astfel creat ne având nici un corespondent pe disc şi sigur că nu va fi nici deschis. Un astfel de obiect necesită deschiderea sau crearea unui nou fişier pe disc după momentul creării sale.

O a doua versiune a constructorului primeşte un parametru, un identificator hFile, care trebuie să corespundă unui fişier deschis.Această formă poate fi utilizată pentru ataşarea unui obiect CFile la un fişier deja deschis.

A treia variantă de constructor necesită doi parametrii: primul este un şir care conţine calea şi numele fişierului pe care dorim să-l deschidem sau să-l creăm, iar cel de-al doilea reprezintă o combinaţie de indicatori de deschidere a fişierului.

CFile, asemenea multor clase MFC, încapsulează un indicator de fişier folosit de nucleul Win32 al sistemului de operare. Acest indicator poate fi accesat direct, aflându-se în variabila membru m_hFile a obiectului CFile. Dacă fu a fost creat sau deschis nici

Page 83: Programare Orientata Pe Obiecte

82

un fişier valid această variabilă va avea valoarea CFile::hFileNull, iar în caz contrar va reţine indicatorul asociat fişierului curent.

33..1166..22 DDeesscchhiiddeerreeaa ffiişşiieerreelloorr

Una din cele mai importante operaţii în manipularea fişierelor este deschiderea iniţială a fişierului. Prin specificarea unor diferiţi indicatori se poate anunţa anticipat modul în care se va lucra cu fişierul specificat. Se poate opta pentru o deschidere a fişierului numai pentru scriere, sau numai pentru citire sau atât pentru scriere cât şi pentru citire. Funcţia membru Open() permite deschiderea unui fişier în unul din modurile specificate mai jos, prin transmiterea corespunzătoare a celui de-al doilea parametru.

Tipuri de indicatori:

CFile::modeCreate - Crează un fişier chear dacă mai există unul cu numele specificat

CFile::modeNoTruncate - Poate fi combinat cu CFile::modeCreate efectul fiind cel de a nu crea fişierul dacă el mai există pe disc.

CFile::modeRead - Fişierul va fi deschis doar pentru citire, scrierea fiind interzisă

CFile::modeWrite - Fişierul va fi deschis exclusiv pentru scriere, citirea datelor nu se poate realiza.

CFile::modeReadWrite - Fişier deschis atât în scriere cât şi în citire. CFile::shareDenyNone - Fişierul poate fi citit şi scris şi de alte procese. CFile::shareExclusive - Fişierul nu poate fi scris sau citit de alte procese atât timp

cât el e exploatat de procesul curent. CFile::shareDenyRead - Nici un proces nu poate să citească fişierul atâta timp cât

acesta este deschis de procesul curent. CFile::shareDenyWrite - Nici un proces nu poate să scrie în fişier atâta timp cât

acesta este deschis de procesul curent. CFile::typeText - Fişier folosit pentru prelucrări specifice conţinutului

textual în cadrul unora dintre clasele derivate CFile::typeBinary - Nu se efectuează nici un fel de prelucrări speciale asupra

caracterelor la citirea sau scrierea acestora în fişier. Acest indicator este necesar doar în unele clase derivate.

Primul parametru al funcţiei Open() reprezintă numele fişierului sau calea completă până la acesta dacă el nu se află în directorul de lucru curent. Unii identificatori pot fi combinaţi.

Pentru crearea unui fişier ne putem confrunta cu două situaţii, anume fişierul poate să nu existe pe disc la locaţia specificată, caz în care va fi creat un nou fişier gol, sau poate să existe deja un fişier cu numele dat, caz în care se va crea un nou fişier iar fişierul existent fa fi şters. Pentru a evita pierderile de date se va apela la o utilizare a indicatorului de deschidere CFile::modeCreate cu un indicator ce atenţionează asupra

Page 84: Programare Orientata Pe Obiecte

83

rescrierilor, este vorba de CFile::modeNoTruncate. Cei doi indicatori pot fi combinaţi folosind operatorul „+” sau operatorul „sau logic( | )” ca mai jos.

CFile fis; Fis.Open(„MyFile”, CFile::modeCreate + CFile::modeNoTruncate) ; sau CFile fis; Fis.Open(„MyFile”, CFile::modeCreate | CFile::modeNoTruncate) ; Dacă fişierul va fi deschis cu succes funcţia Open() va întoarce valoarea TRUE

iar în caz contrar va întoarce valoarea FALSE. Dacă la deschiderea unui fişier apare o eroare, este transmis un pointer la un

obiect CFileException. Cauza erorii poate fi determinată pe baza variabilelor m_cause şi m_IOsError, ambele fiind membre ale obiectului CFileException. Valorile tipice pentru valoarea m_cause sunt CFileException::fileNotFound, CFileException::diskFull şi CFileException::accessDenied, specificând faptul că fişierul nu există, discul este plin şi nu avem spaţiu pentru scriere, sau nu avem drept de scriere.

33..1166..33 CCiittiirreeaa şşii ssccrriieerreeaa uunnuuii ffiişşiieerr

În funcţie de modul de acces în care a fost deschis fişierul, după deschiderea acestuia se poate trece la efectuarea operaţiilor de citire şi scriere. Pentru efectuarea acestor operaţii clasa CFile pune la dispoziţie funcţiile Read() şi Write(). Operaţiile de scriere şi citire se vor face la o poziţie dată în fişier. După deschiderea fişierului această poziţie este stabilită la începutul acestuia. Dacă se citesc primii 200 de octeţi din fişier poziţia va fi reactualizată, o viitoare citire realizându-se de la poziţia 200.

Funcţia Read() primeşte doi parametrii iar primul dintre aceştia reprezintă adresa unui buffer destinaţie. Orice date citite vor fi depuse în acest buffer. Prin cel de-al doilea parametru se specifică funcţiei Read() câţi octeţi se doreşte a se citi. Numărul de octeţi ce se vor citi nu va depăşi dimensiunea buffer-ului destinaţie, şi va fi mai mare decât zero. Dacă se citesc mai mulţi octeţi decât permite buffer-ul există riscul de a suprascrie zone de memorie vitale ale programului.

Apelul funcţiei Read() întoarce numărul de octeţi citiţi. Valoarea întoarsă poate fi egală cu numărul octeţilor solicitat a se citi, caz în care s-au citit toţi octeţii ceruţi, sau poate fi mai mică decât numărul de octeţi specificat în apel, indicând faptul că până la sfârşitul fişierului sunt mai puţini octeţi decât numărul specificat, operaţia de citire va decurge normal pentru octeţii ce se pot citi. Dacă funcţia Read() întoarce valoarea zero, poziţia în fişier este la sfârşitul acestuia, operaţia nu se poate deci realiza.

Exemplu: CFile myFile(„MyFile.tit”,CFile::modeCreate); char buff[200]; UINT nrBytesRead; nrBytesRead = myFile.Read(buff,sizeof(buff));

Page 85: Programare Orientata Pe Obiecte

84

În exemplul de mai sus se citesc din fişierul myFile în buffer-ul buff un număr de octeţi egal cu dimensiunea buffer-ului. Dacă după citire valoarea lui nrBytesRead este mai mare decât zero, s-au citit un număr de octeţi egal cu valoarea întoarsă, altfel nu s-a citit nimic. După citire se realizează un avans al poziţiei curente în cadrul fişierului cu un număr de poziţii egal cu valoarea întoarsă de funcţia Read().

Scrierea în fişier se realizează la poziţia curentă. Funcţia Write() primeşte doi parametri, primul parametru este un buffer în care se găseşte informaţia ce va fi scrisă iar cel de-al doilea este un număr întreg ce specifică numărul de octeţi ce va fi scris din buffer. După scriere are loc un avans al poziţiei curente în fişier cu un număr de poziţii egal cu numărul de octeţi scrişi.

33..1166..44 MMaanniippuullaarreeaa ppoozziiţţiieeii ccuurreennttee îînn ffiişşiieerr Există o poziţie curentă în cadrul fişierului care este folosită ca funcţie de

început pentru funcţiile Read() şi Write(). La scrierea în fişier de informaţie cu o dimensiune constantă a înregistrărilor, se poate dori scrierea într-un loc specificat în fişier şi nu neaparat la începutul fişierului sau la poziţia curentă.

Poziţia actuală a cursorului în cadrul fişierului poate fi determinată utilizând funcţia GetPosition(), care întoarce o valoare de tip DWORD. Această poziţie poate fi de asemenea modificată prin intermediul funcţiilor Seek(), SeekToBegin şi SeekToEnd(), care plasează cursorul la o poziţie anume, la începutul sau, respectiv, la sfârşitul fişierului. Singura dintre aceste funcţii care necesită parametrii este funaţia Seek(), aceasta necesitând doi parametrii. Primul parametru reprezintă numărul de octeţi cu care se face deplasarea şi este un număr de tip LONG, iar cel de-al doilea este poziţia relativă faţă de care se face deplasarea poate lua una din valorile:

CFile::begin - deplasare relativă la începutul fişierului. CFile::end - deplasare relativă la sfârşitul fişierului. CFile::current - deplasare relativă la poziţia curentă. Deplasarea se poate face în faţă sau în spate relativ la punctul specificat, prin

specificarea numărului de octeţi cu care se face deplasarea neînsoţit de semnul „-” sau respectiv însoţit de semnul „-”.

Exemplu de deplasare înainte faţă de începutul fişierului cu 80 de octeţi: MyFile.Seek(80,CFile::begin); Exemplu de deplasare înapoi faţă de poziţia curentă a fişierului cu 30 de octeţi: MyFile.Seek(-30,CFile::current);

33..1166..55 IInnffoorrmmaaţţiiii ddeesspprree ffiişşiieerr Există un set de funcţii ce furnizează informaţii despre fişierul deschis curent. GetLength() întoarce lungimea fişierului deschis curent ca o variabilă de tip

DWORD, de asemenea există o funcţie SetLength() care trunchiază sau extinde fişierul la dimensiunea specificat ca parametru. Se poate spune despre cele două funcţii că sunt funcţii pereche. Funcţia GetStatus() este o funcţie ce generează informaţii despre

Page 86: Programare Orientata Pe Obiecte

85

momentul creării, al ultimei modificări ca şi informaţii despre atribute de citire şi scriere. Această funcţie are două variante. Una dintre acestea operează asupra fişierului deja deschis punând informaţiile într-o variabilă de tip CFileStatus transmisă va parametru, ea este o funcţie membru. Cea de-a doua variantă primeşte ca prim parametru numele fişierului si va genera informaţiile într-o variabilă de tip CFileStatus transmisă prin referinţă ca al doilea parametru. Această a doua variantă este o funcţie prietenă (friend) pentru clasa CFile.

Exemplu de utilizare: CFileStatus varStat; CFile::GetStatus(„c:\\fis.tit”, varStat); În acest moment informaţiile sunt stocate în variabila varStat şi por fi utilizate în

diverse operaţii. Structura unei variabile CFileStatus este: CTime m_mtime - Data şi ora ultimei modificări CTime m_atime - Data şi ora ultimei accesări CTime m_ctime - Data şi ora creării LONG m_size - Dimensiunea în octeţi a fişierului BYTE m_attribute - Atribute de citire, scriere etc. char m_szFullName - Numele fişierului şi calea completă

33..1166..66 RReeddeennuummiirreeaa şşii şştteerrggeerreeaa uunnuuii ffiişşiieerr Există două funcţii statice care au ca efect redenumirea şi ştergerea fişierelor:

Rename() şi Remove(). Rename() primeşte doi parametrii: vechiul nume al fişierului şi viitorul nume al fişierului. Fiind o funcţie statică, nu este nevoie să se deschidă un fişier, ci este suficientă prefixarea apelului cu operatorul de domeniu ca mai jos:

CFile::Rename(„CFile:\\myFile.tit”, ”CFile::\\newFile.tit”); O funcţie statică este o funcţie membru a unei clase C++ care nu are nevoie de

un obiect context. Funcţia Remove() este de asemenea o funcţie statică ce primeşte ca parametru

numele fişierului se trebuie şters. CFile::Remove(„c:\\myFile.tit”); Pentru a se şterge un fişier este necesar ca obiectul de tip CFile asociat

acestuia(dacă este cazul) să fie închis, adică acestui obiect să i se apeleze pai întâi funcţia membru Close().

De exemplu pentru a închide fişierul fis.tit de mai jos procedăm astfel: CFile fis; fis.Open(„fis.tit”,CFile::modeCreate); . . . fis.Close() CFile::Remove(„fis.tit”); Ambele funcţii generează o excepţie dacă eşuează dintr-un anumit motiv.

Page 87: Programare Orientata Pe Obiecte

86

3.17 Elemente de grafică în Visual C++

Grafica pe calculator se ocupă de sinteza picturală a unor obiecte imaginare sau

reale pe baza unor modele construite cu ajutorul calculatorului. Un proverb chinez spune că „O imagine valorează cât zece mii de cuvinte”, şi într-adevăr o imagine poate fi oricând o metodă de a realiza o comunicare între două persoane care nu cunosc o limbă comună în care să converseze.

33..1177..11 NNooţţiiuunneeaa ddee ccoonntteexxtt ddiissppoozziittiivv Un context dispozitiv reprezintă suprafaţa pe care se reprezintă toate punctele,

liniile, pătratele, fonturile, culorile şi tot ceea ce se observă pe ecran. Cuvântul dispozitiv din context dispozitiv reprezintă faptul că se poate desena pe ecran, la imprimantă, la plotter, într-o cască pentru realitate virtuală sau pe orice alt dispozitiv de desenare în două dimensiuni.

În esenţă, un context dispozitiv este o structură Windows care conţine atribute ce descriu opţiunile implicite de desenare folosite în orice operaţie grafică, aşa cum ar fi trasarea unei linii. Spre deosebire de toate celelalte structuri din Windows, un program nu va putea niciodată să acceseze structura unui context dispozitiv, el va putea însă să modifice opţiunile acestei structuri prin intermediul unor funcţii de acces standard.

Microsoft a standardizat întreg suportul de desenare şi dispozitive în cadrul sistemului de operare Windows. Dacă avem la dispoziţie o imprimantă model foarte recent, prin simpla instalare a driver-elor pentru această imprimantă se poate lista din orice aplicaţie, fără discriminare. Contextul dispozitiv este cel ce le uneşte şi oferă întreg suportul pentru desenare.

Contextele dispozitiv efective sunt obiecte GDI(Graphics Device Interface – interfaţă cu dispozitivele grafice). GDI reprezintă o mulţime de funcţii ce se află într-o bibliotecă DLL în inima sistemului de operare. Aceste funcţii fac legătura dintre apelurile funcţiilor de desenare pe care le efectuăm şi driver-ele de dispozitiv care comunică cu dispozitivele hardware pentru a transforma totul în realitate, sub formă de lumină sau cerneală.

33..1177..22 UUttiilliizzaarreeaa ccllaasseeii CCDDCC

Clasele de context dispozitiv folosite în aplicaţii au întotdeauna clasa de bază

CDC. Această clasă conţine doi indicatori legaţi de obiectul GDI aflat dedesubt: indicatorul m_hDC şi indicatorul m_hAttribDC. m_hDC este indicatorul de context dispozitiv care face legătura dintre clasă şi obiectul GDI pentru a gestiona ieşirile

Page 88: Programare Orientata Pe Obiecte

87

funcţiilor de desenare. m_hAttribDC este indicatorul de context dispozitiv care leagă toate informaţiile de atribute, cum ar fi culori şi modurile de desenare. Este bine de reţinut faptul că la apelarea unor funcţii ca GetDC() sau ReleaseDC() (care obţin şi respectiv eliberează contextul dispozitiv pentru o fereastră) aceşti indicatori GDI sunt ataşaţi şi detaşaţi de clasa CDC.

33..1177..33 CCoonntteexxttuull ddiissppoozziittiivv cclliieenntt Clasa CClientDC automatizează apelurile GetDC() şi ReleaseDC(). La

construirea unui obiect CClientDC se va transmite ca parametru un pointer la o fereastră, iar clasa va folosi apoi acest pointer pentru a accesa contextul dispozitiv al ferestrei respective. La distrugerea clasei, aceasta apelează automat ReleaseDC(), aşa că nu ne va preocupa acest lucru. Termenul Client se referă la suprafaţa normală de desenare a ferestrei, există de asemenea o clasă corespunzătoare, CWindowDC, ce ne permite ce ne permite să accesăm şi bara de titlu şi marginile ferestrei, dar aceasta se foloseşte rar deoarece mai toate aplicaţiile moderne se rezumă la a desena în propriile zone client.

Clasa CClientDC reţine indicatorul către fereastra asociată în variabila sa membru m_hWnd . Poate fi transmis indicatorul de fereastră m_hWnd(de tip HWND) funcţiei Attach() a unui obiect CWnd pentru a accesa funcţiile ferestrei asociate.

Se propune în continuare să scriem o funcţie simplă pe care o vom numi MyOnDraw() şi care va avea scopul de a desena în colţul din stânga, sus al ferestrei client, drapelul României de dimensiune 300x300 pixeli.

Se va presupune că avem deschis un proiect de tip DialogBased (acest lucru presupune crearea unui nou proiect de tip MFC AppWizard(exe) iar la primul pas se va valida opţiunea DialogBased) al cărui nume este Drapel. De asemenea se va merge în ferestrei spaţiului de lucru a proiectului(vezi 2.3), în secţiunea ClassView şi se va face clic dreapta pe clasa CDrapelView iar din meniul contextual afişat va fi aleasă opţiunea Add Menber Variable…. În caseta de dialog deschisă, veţi opta pentru tipul funcţiei ca fiind void iar numele îl veţi seta MyOnDraw(). Acţionaţi apoi butonul OK şi faceţi dublu clic pe numele funcţiei (OnDraw()) ce va apărea acum ca metodă a clasei CDrapelView în secţiunea ClassView a ferestrei spaţiului de lucru a proiectului. Veţi fi poziţionat astfel automat pe definiţia funcţiei MyOnDraw().

Funcţia va fi: void CDrapelView:: MyOnDraw() { //TODO: Add your control notification handler code } ea va fi modificată astfel: void CDrapelView:: MyOnDraw() {

Page 89: Programare Orientata Pe Obiecte

88

//TODO: Add your control notification handler code CClientDC cDC(this); for(int i=0;i<=300;i++) for(int j=0;j<=300;j++) { if (i<100) cDC->SetPixel(i, j, (255<<16)|(0<<8)|0); else if(i<200) cDC->SetPixel(i, j, (0<<16)|(255<<8)|255); else cDC->SetPixel(i, j, (0<<16)|(0<<8)|255) } } Prima linie introdusă declară un obiect de tip CClientDC numit cDC şi prin

utilizarea pointer-ului this îi atribuie acestuia CDC-ul ferestrei curente(al casetei de dialog ). La sfârşitul funcţiei nu se mai apelează ReleaseDC() deoarece am spus că acesta este apelat automat de destructorul obiectului de tip CClientDC.

Cele două for-uri imbricate parcurg pixel cu pixel zona pătrată bidimensională, ce va fi desenată. Dacă avem o linie de pixeli între pixel-ul 0 şi 100 vom desena pixelul în roşu, dacă aceasta are valoarea între 101 şi 200 pixel-ul va lua culoarea galben, iar dacă este între 201 şi 300 vom desena un pixel albastru. Acesta este rolul cascadei de if-uri din cadrul funcţiei.

Stabilirea valorii unui pixel se setează cu ajutorul funcţiei SetPixel() care va primi trei parametrii de tip LONG. Primii doi parametrii reprezintă coordonatele pixelului (linia şi coloana).Cel de-al treilea parametru reprezintă valoarea culorii ce va fi afişată pentru acel pixel. Ecranul are un număr foarte mare de pixeli, numărul acestora este stabilit prin reglarea rezoluţiei. Aceasta se face pe orice sistem de calcul ce foloseşte sistemul de operare Windows printr-un click dreapta pe desktop, selectând apoi Properties din meniul contextual, şi mergând în secţiunea Settings. Se va observa un glisor în caseta de grupare cu numele Screen Resolution, prin tragerea acestui glisor se poate stabili rezoluţia ecranului. Aceasta este de obicei de 1024 / 768 sau 800 / 600. Caseta de dialog pe care vom desena va ocupa o porţiune din pixelii întregului ecran. Pixelul din stânga-sus al casetei de dialog va avea poziţia (0,0) numărul liniei crescând de sus în jos iar cel al coloanei de la stânga le dreapta.

Există trei culori complementare (roşu, verde şi albastru) iar pentru fiecare din aceste culori există câte un tun de electroni în monitorul calculatorului. Prin trimiterea cu o anumită intensitate a electronilor corespunzători fiecăreia din cele trei culori, spre un acelaşi punct de pe ecran(numit pixel (Picture element)) în acest punct se va crea „impresia” unei culori. De exemplu dacă intensitatea cu care sunt trimişi electronii este maximă pentru toate cele trei tunuri, atunci vom obţine o culoare albă a pixel-ului respectiv, iar dacă spre un pixel nu se trimit nici un fel de electroni, culoarea acestuia va fi neagră. Dacă doar tunul pentru roşu trimite electroni spre un pixel, culoarea acestuia va fi roşie. Observăm ce importanţă are intensitatea cu care sunt trimişi electronii în formarea culorilor pe ecran. Astfel fiecărui pixel i se pun în corespondenţă trei variabile de tip int, asociate celor trei culori complementare şi fiecare variabilă poate lua o valoare între 0 şi 255. Valorile acestor variabile reprezintă intensitatea nuanţei de roşu, verde şi respectiv de albastru pentru pixel-ul respectiv.

Pentru a se economisi memorie, în locul a trei variabile de tip int se va asocia fiecărui pixel o singură variabilă de tip LONG. Presupunând că variabilele de tip int

Page 90: Programare Orientata Pe Obiecte

89

asociate unui pixel sunt r,g,b pentru red(roşu), green(verde) respectiv blue(albastru), valoarea variabilei de tip LONG (s-o notăm rgb) ce se va ataşa pixel-ului se va calcula ca mai jos:

rgb = (((r) << 16) | ((g) << 8) | (b)) unde << este operatorul de deplasare la stânga pe biţi, iar | este „sau” pe biţi. Operaţia inversă se realizează astfel: r = ((rgb) >> 16) & 0xff g = ((rgb) >> 8) & 0xff b = (rgb) & 0xff unde >> e operatorul de deplasare la dreapta pe biţi, iar & e „şi” pe biţi. 0xff în hexazecimal este 255 în zecimal.

33..1177..44 CCoonntteexxttee ddiissppoozziittiivv ddee mmeemmoorriiee Un context dispozitiv de memorie este un context dispozitiv care nu are asociat

nici un dispozitiv. Aceste contexte dispozitiv se folosesc de obicei împreună cu un context dispozitiv obişnuit pentru copierea şi lipirea unor zone pe ecran. Este posibilă crearea unui context dispozitiv de memorie compatibil cu un context dispozitiv de afişare. Apoi puteţi să copiaţi în memorie imaginile care nu mai sunt afişate, aducându-le înapoi în contextul dispozitiv de afişare atunci când este nevoie.

Nu există nici o clasă MFC care să implementeze un context dispozitiv de memorie; aşa ceva nu este necesar deoarece clasa CDC este foarte potrivită. Fiind vorba despre o clasă context dispozitiv CDC o puteţi folosi pentru desenare ca de obicei, singura diferenţă este că veţi desena în memorie iar utilizatorul nu va vedea nimic până ce nu copiaţi rezultatele în contextul dispozitiv de afişare al unei ferestre.

Se poate crea un context dispozitiv de memorie compatibil şi să modifice codul de desenare pentru a lucra cu memoria şi nu cu ecranul. Odată creată imaginea în memorie o putem copia pe ecran cu un apel al funcţiei BitBlt(). Pentru a crea o imagine în memorie trebuie să creăm nu numai un context dispozitiv de memorie ci şi un bitmap asociat acestuia. Spre deosebire de contextele dispozitiv de afişare, care sunt legate de ferestre, un context dispozitiv de memorie nu dispune automat de un bitmap compatibil cu ecranul.

Un bitmap dintr-un context dispozitiv de memorie poate fi mult mai mic sau mult mai mare decât contextul dispozitiv de afişare asociat. Prin crearea unui bitmap mare puteţi obţine o suprafaţă de desenare cu o rezoluţie mult mai înaltă decât a ecranului. Trebuie să reţineţi, însă, că un bitmap mai mare are nevoie de mai multă memorie de la sistem. În situaţia în care bitmap-ul necesită mai mult decât memoria RAM existentă , sistemul va fi încetinit considerabil prin utilizarea memoriei virtuale în scopul compensării lipsei de RAM. Memoria virtuală este o zonă de pe disc utilizată ca şi memorie pentru a suplini memoria RAM. Memoria virtuală este înceată deoarece segmente mari de memorie, numite pagini sunt schimbate permanent între RAM şi disc.

Se propune în continuare să rescriem funcţia MyOnDraw() care a fost creată la punctul 2.7.1.2 al lucrării, utilizând un context dispozitiv de memorie:

Page 91: Programare Orientata Pe Obiecte

90

void CDrapelView:: MyOnDraw() { //TODO: Add your control notification handler code CClientDC cDC(this); //Creăm un context dispozitiv de memorie compatibil CDC memDC; memDC=CreateCompatibleDC(&cDC); //Determinăm zona client CRect rc; GetClientRect(&rc); //Creăm un bitmap compatibil CBitmap bmp; Bmp.CreateCompatibleBitmap(&cDC, rc.Width(), rc.Height()); //selectăm bitmap-ul în cadrul contextului dispozitiv de memorie memDC.SelectObject(&bmp); //desenăm for(int i=0;i<=300;i++) for(int j=0;j<=300;j++) { if (i<100) cDC->SetPixel(i, j, (255<<16)|(0<<8)|0); else if(i<200) cDC->SetPixel(i, j, (0<<16)|(255<<8)|255); else memDC->SetPixel(i, j, (0<<16)|(0<<8)|255) } //copiem memDC în cDC cDC.BitBlt(0,0,300,300,&memDC,0,0,SRCINVERT) } Este indicată utilizarea funcţiei BitBlt() în loc de o copiere directă, pixel cu

pixel deoarece este cu mult mai rapidă. Numele acestei funcţii vine de la un termen hardware mai vechi, bit blitting, care

se referă la un circuit capabil să copieze rapid o zonă de memorie dintr-un loc în altul. În unele cazuri s-ar putea ca placa grafică să efectueze exact acest gen de copiere

la apelul funcţiei dar în alte cazuri copierea va fi realizată de procesor. Funcţia BitBlt() primeşte următorii opt parametrii:

- linia din contextul dispozitiv de memorie începând cu care se va face copierea, tipul este long

- coloana din contextul dispozitiv de memorie începând cu care se va face copierea, tipul este long

- linia din contextul dispozitiv de memorie până la care se va face copierea, tipul este long

Page 92: Programare Orientata Pe Obiecte

91

- coloana din contextul dispozitiv de memorie până la care se va face copierea, tipul este long

- un pointer la contextul dispozitiv de memorie. - Linia de la care începând are loc crearea imaginii în contextul dispozitiv asociat

echipamentului fizic. - Coloana de la care începând are loc crearea imaginii în contextul dispozitiv

asociat echipamentului fizic. - Un parametru ciudat SRCINVERT care ne specifică faptul că imaginea va fi

copiată inversând culorile. În consecinţă imaginea din fereastra destinaţie va avea un colorit diferit, ceea ce demonstrează faptul că imaginea va fi copiată mai întâi în memorie şi abia apoi în contextul dispozitiv al casetei de dialog. Apelul funcţiei BitBlt() are loc întotdeauna pentru un obiect de tip context

dispozitiv.

33..1177..55 PPeenniiţţee şşii ppeennssuullee

Peniţele reprezintă unul dintre obiectele GDI(Graphics Device Interface) de bază.; ele sunt implementate chiar în inima sistemului Windows şi practic se află acolo de la bun început. Înainte de a desena ceva trebuie să selectaţi sau să creaţi o peniţă potrivită pentru operaţia vizată.

Peniţele sunt folosite pentru trasarea de linii şi curbe. Atunci când desenaţi figuri pline, peniţele sunt utilizare pentru trasarea conturului iar pensulele pentru colorarea interiorului. Sunt situaţii când nu doriţi să umpleţi figurile ci doar să trasaţi conturul. Aceste situaţii se rezolvă prin folosirea unei pensule transparente sau vide împreună cu funcţiile de desenare ale figurii respective.

33..1177..66 UUttiilliizzaarreeaa ccllaasseeii CCPPeenn

MFC oferă o clasă de încapsulare CPen care simplifică manipularea peniţelor(ca

obiecte de desenare). Această clasă conţine obiectul GDI aflat la bază şi se ocupă de alocarea şi eliberarea acestuia. Pentru crearea unei peniţe trebuie să declaraţi un obiect corespunzător şi să transmiteţi nişte parametrii de iniţializare. Linia de cod de mai jos creează o peniţă continuă de culoare roşie.

CPen penRed(PS_SOLID,3,RGB(255,0,0)); Parametrul PS_SOLID precizează tipul peniţei. Este posibilă crearea unei

varietăţi de peniţe prin intermediul tipurilor disponibile. Tipurile disponibile pentru peniţe sunt: PS_SOLID -linii continue PS_DASH -linii întrerupte(segmentate) PS_DOT -linii întrerupte(punctate) PS_DASHDOT -linii întrerupte(de tip linie-punct)

Page 93: Programare Orientata Pe Obiecte

92

Grosimea sau lăţimea peniţei poate fi modificată prin intermediul celui de-al

doilea parametru. Valoarea1 pentru acest parametru va avea ca rezultat o peniţă a cărei lăţime este de 1 pixel (cea mai mică posibilă). Se poate specifica însă o grosime mai mare pentru peniţă.

Este bine de reţinut că stilurile de peniţe cu linii întrerupte se pot stabilii doar pentru peniţe ce au o grosime unitară(egală cu 1).

Al treilea parametru care poate fi specificat la crearea unei peniţe este culoarea acesteia. Tipul acestui parametru va fi de tip COLORRREF, care nu este altceva decât un număr pe 32 de biţi care reprezintă o combinaţie a componentelor de roşu verde şi albastru ce formează o culoare. Specificarea directă a valorilor pentru o variabilă COLORREF este uneori greoaie motiv pentru care se va utiliza o macrodefiniţie numită RGB care vă vine în ajutor.

Iată cum se utilizează în cod această macrodefiniţie: COLORREF myrgb = RGB(255,0,255); CPen myPen(PS_DASH,1,myrgb); Deşi codificarea culorilor pe 24 de biţi (True Color) vă permite să operaţi cu

16,7milioane de culori şi nuanţe, culorile afişate efectiv depind de caracteristicile plăcii grafice şi ale monitorului. În cazul în care modul grafic curent suportă mai puţine culori, Windows va încerca să compenseze prin crearea unei palete de culori care să reprezinte cât mai bine culorile afişate curent de sistem. Uneori va recurge la amestecarea a două culori într-un model haşurat de pixeli cu scopul de a produce pe baza culorilor disponibile un rezultat cât mai apropiat de culoarea dorită.

Macrodefiniţia RGB primeşte trei parametrii care reprezintă intensităţile nuanţelor de roşu, verde, respectiv albastru. Fiecare dintre cei trei parametrii poate lua valori între 0 şi 255, unde valoarea 0 reprezintă lipsa culorii respective, iar 255 reprezintă intensitatea maximă a culorii respective.

Nu este întotdeauna necesar să specificaţi caracteristicile unei peniţe, deoarece Windows are disponibile câteva peniţe de stoc. Acestea sunt configurate deja cu parametrii impliciţi şi sunt utilizate la desenarea suprafeţei de lucru şi a controalelor. Pentru a putea utiliza o astfel de peniţă veţi declara un obiect de tip CPen dar nu veţi transmite nici un parametru. Veţi apela apoi CreateStockObject(), care va stabili caracteristicile peniţelor la cele ale obiectului din stoc specificat. Iată, de exemplu, cum puteţi alege din stoc peniţa neagră:

CPen blackPen; BlackPen.CreateStockObject(BLACK_PEN); Stilul NULL_PEN ar putea părea puţin ciudat, deoarece nu prea are sens să

desenăm o linie care practic nu se va vedea pe ecran, dar atunci când dorim să desenăm o figură plină al cărui contur nu este vizibil această peniţă vom vedea că ne este de real folos.

Peniţele de stoc sunt cele de mai jos: BLACK_PEN - trasează linii negre WHITE_PEN - trasează linii albe NULL_PEN - trasează cu culoarea curentă de fundal Pentru a putea să desenaţi cu peniţele create este necesar ca acestea să fie

selectate în cadrul contextului dispozitiv. Un context dispozitiv are un slot pentru peniţe

Page 94: Programare Orientata Pe Obiecte

93

în care se poate afla o singură peniţă la un moment dat. Aceasta este peniţa cu care desenaţi. La selectarea unei noi peniţe în contextul dispozitiv, peniţa anterioară este eliberată. Pentru selectarea noii peniţe vom apela la o metodă a contextului dispozitiv, anume la metoda SelectObject().

Selectarea unui obiect de tip peniţă se va face ca în exemplul de mai jos, care

încearcă să deseneze un obiect roşu şi unul albastru. Funcţia definită mai jos va putea fi apelată la apăsarea unui buton, de exemplu, având ca efect desenarea celor două obiecte pe contextul dispozitiv.

void CDesenView::MyOnDraw() { CClientDC cDC(this); CPen redpen(PS_SOLID,3,RGB(255,0,0)); CPen *pOldPen=NULL; pOldPen=cDC->SelectObject(&redpen); // ……..desenez obiectul roşu CPen bluepen(PS_SOLID,3,RGB(0,0,255)); cDC->SelectObject(&bluepen); // ……..desenez obiectul albastru cDC->SelectObject(pOldPen); } pOldPen este un pointer la un obiect de tip CPen şi l-am utilizat pentru a păstra

în el valoarea peniţei iniţiale pentru a putea reveni la aceasta după terminarea desenării obiectului. Metoda SelectObject() are proprietatea de a întoarce un pointer la obiectul de tip CPen care era selectat înainte de apelul funcţiei. De aceea am setat valoarea lui pOldPen pe valoarea întoarsă de SelectObject(). Pentru cel de-al doilea apel nu este necesară repetarea procedeului ci este chiar contraindicată deoarece funcţia SelectObject()ar întoarce de această dată un pointer la obiectul redpen deoarece acesta era selectat înaintea apelului. Ultimul rând de cod are rolul de a selecta din nou vechia peniţă pentru ca eventualele desenări ce vor mai avea loc să se realizeze cu aceasta.

Odată ce nu mai aveţi nevoie de peniţele ce le-aţi creat, trebuie să le ştergeţi. În acest fel este eliberat obiectul GDI aflat dedesubt şi sunt disponibilizate resurse preţioase ale sistemului. Clasa CPen se ocupă automat de toate acestea la distrugerea sa, dar aţi putea dori să efectuaţi explicit aceste operaţii în cazul în care vreţi să refolosiţi un obiect de tip CPen cu alţi parametrii(apelând CreatePen()).]

Deşi MFC oferă clase elegante care încapsulează obiectele grafice, aşa cum este CPen, obiectele propriu-zise sunt identificate prin indicatori care sunt variabile membru ale claselor MFC de încapsulare. De fiecare dată când apelaţi o funcţie SelectObject()

Page 95: Programare Orientata Pe Obiecte

94

sau DeleteObject() a unui obiect context dispozitiv, un astfel de indicator către un obiect grafic este de fapt selectat sau şters la nivelul Win32.

Funcţia membru care distruge obiectul GDI aflat la bază este DeleteObject().Codul următor prezintă un obiect peniţă care este creat, folosit, şters şi apoi creat din nou.

void MyDesenView::MyOnDraw() { CClientDC pDC(this); CPen penMainDraw(PS_DASH,1,RGB(128,255,255)); CPen *pOldPen = NULL; POldPen=pDC->SelectObject(&penMainDraw)); //Desenăm cu peniţa segmentată pDC->SelectObject(pOldPen); penMainDraw.DeleteObject(); penMainDraw.CreatePen(PS_SOLID,5,RGB(200,20,5)) pOldPen=pDC->SelectObject(&penMainDraw); //Desenăm cu peniţa continuă pDC->SelectObject(pOldPen); } DeleteObject() este automat apelată atunci când penMainDraw este ştearsă odată

cu închiderea funcţiei. Dacă se apelează funcţia DeleteObject() pentru o peniţă în timp ce aceasta este

încă selectată într-un context dispozitiv, obiectul de tip GDI corespunzător va fi distrus şi orice operaţii de desenare ulterioare care implică respectiva peniţă pot duce la prăbuşirea aplicaţiei sau la rezultate neaşteptate.

33..1177..77 TTrraassaarreeaa ddee lliinniiii şşii ffiigguurrii ccuu aajjuuttoorruull ppeenniiţţeelloorr Pentru a desena ceva cu ajutorul peniţelor avem nevoie de un context dispozitiv.

Contextul dispozitiv oferă mecanismul pentru desenarea cu aceleaşi obiecte în cadrul unei întregi mulţimi de dispozitive, rezultatele obţinute fiind identice.

Fereastra dintr-o aplicaţie SDI poate fi un suport ideal pentru tendinţele dumneavoastră artistice. Prezint mai jos modul în care poate fi creată o aplicaţie SDI care foloseşte o reprezentare simplă destinată desenării.

1. Efectuaţi clic pe meniul File şi selectaţi New. 2. Selectaţi pagina Projects. Din lista cu tipurile de proiecte alegeţi MFC

AppWizard(exe). 3. Acum efectuaţi un clic în caseta Project Name şi introduceţi numele

proiectului. 4. Efectuaţi un clic pe OK. Va fi afişată caseta de dialog MFC AppWizard –

Step 1

Page 96: Programare Orientata Pe Obiecte

95

5. Selectaţi Single Document şi efectuaţi un clic pe Finish. În consecinţă, va fi creată infrastructura unei aplicaţii SDI care va utiliza clasa de bază Cview pentru a deriva clasa reprezentare a noii aplicaţii

6. Efectuaţi un clic pe OK în caseta de dialog New Project Information şi AppWizard va crea noul proiect şi fişierele sale sursă.

Un context dispozitiv păstrează coordonatele unei poziţii curente a peniţei. La

desenarea liniilor, acestea sunt trasate între poziţia curentă şi poziţia specificată, după care poziţia specificată devine noua poziţie curentă.

Contextele dispozitiv dispun de o funcţie membru , MoveTo() care stabileşte această poziţie curentă. MoveTo() se apelează cu doi parametrii care specifică poziţia pe orizontală şi pe verticală.

Poziţia curentă poate fi determinată prin apelul funcţiei GetCurrentPosition() a contextului dispozitiv, aceasta întorcând poziţia curentă sub forma unui obiect CPoint. Obiectele de tip CPoint conţin doi membri întregi care specifică numărul liniei şi cel al coloanei şi aceştia sunt x şi respectiv y.

33..1177..88 DDeesseennaarreeaa ddee lliinniiii Contextele dispozitiv oferă o metodă LineTo(), asemănătoare metodei

MoveTo(). Parametrii primiţi sunt aceeaşi şi efectul este trasarea unei linii de la poziţia curentă la poziţia specificată. După trasare poziţia curentă va deveni automat poziţia indicată de parametrii funcţiei LineTo(). Vom folosi funcţia OnDraw() a unui nou document de tipul SDI pentru a desena un triunghi pe ecran. Pentru aceasta vom modifica funcţia OnDraw() în maniera următoare:

void CMyDesenView::OnDraw(CDC * pDC) { CmyDesenDoc * pDoc = GetDocument(); ASSERT_VALID(pDoc); //TODO: add draw code for native data here //Creăm o peniţă CPen penRed(PS_DOT, 1, RGB(255,0,0)); CPen *pOldPen = NULL; pOldPen= pDC->SelectObject(&penRed); pDC->MoveTo(50,100); // Desenăm latura de bază pDC->LineTo(100,100); // Desenăm prima latură laterală pDC->LineTo(75,50); // Desenăm a doua latură laterală pDC->LineTo(50,100); //Selectăm la loc peniţa iniţială pDC->SelectObject(pOldPen); }

Page 97: Programare Orientata Pe Obiecte

96

33..1177..99 DDeesseennaarreeaa ddee cceerrccuurrii şşii eelliippssee Un cerc nu este decât o elipsă care în loc să fie înscrisă într-un dreptunghi este

înscrisă într-un pătrat, deci pentru ambele figuri avem nevoie de aceeaşi funcţie a contextului dispozitiv. Funcţia Ellipse() are două forme. Prima formă primeşte un dreptunghi transmis ca obiect CRect. Cea de-a doua formă necesită patru parametrii, reprezentând coordonatele pe orizontală şi pe verticală ale colţului stânga sus şi ale colţului dreapta jos.

Pentru a desena o elipsă se ca selecta în prealabil a peniţă şi se poate scrie în cadrul funcţiei OnDraw() despre care am mai discutat, o secvenţă de forma:

// preluăm dreptunghiul suprafeţei contextului dispozitiv CRect rcClient; GetClientRect(&rcClient); //creăm un dreptunghi în centrul dreptunghiului client de lungime şi lăţime 0. CRect rcEllipse(rcClient.CenterPoint(), rcClient.CenterPoint()); //redimensionăm dreptunghiul la o dimensiune de 10/10 rcEllipse.InflateRect(10,10); //desenăm elipsa pDC->Ellipse(rcEllipse);

33..1177..1100 DDeesseennaarreeaa ddee ccuurrbbee PolyBezier() este numele unei funcţii care vine de la matematicianul Bézier, care

a redus curbele la polinoame cubice. Polinomul descrie linia curbă, iar faptul că este cubic înseamnă că există un punct de început, unul de sfârşit şi două puncte de control înspre care este flexată curba prin interpolare. Particula Poly arată că este posibilă trasarea mai multor curbe conectate între ele. Funcţia primeşte un vector de obiecte CPoint(cel puţin patru) şi numărul de puncte din vector.

Din cauza calculelor suplimentare care sunt necesare pentru trasarea liniilor curbe , timpul necesar este substanţial mai mare decât în cazul liniilor drepte. Probabil că nu se remarca diferenţa de viteză dacă nu se trasează decât câteva curbe, dar aceasta poate deveni semnificativă dacă aplicaţia va trasa un număr mare de curbe.

Următorul fragment de program desenează o curbă bezier. CPoint pB[4]; pB[0].x=10; pB[0].y=10;

Page 98: Programare Orientata Pe Obiecte

97

pB[1].x=5; pB[1].y=5; pB[2].x=10; pB[2].y=3; pB[3].x=50; pB[3].y=10; pDC->PolyBezier(pB,4); De asemenea Visual C++ oferă o funcţie Polyline() pentru trasarea curbelor

polinomiale, care primeşte aceeaşi parametrii ca şi funcţia PolyBezier().

33..1177..1111 CCrreeaarreeaa ppeennssuulleelloorr Peniţele sunt utile pentru trasarea contururilor de figuri, dar umplerea acestora

cu culoare necesită o pensulă. Majoritatea funcţiilor de desenare GDI folosesc atât o peniţă cât şi o pensulă. Peniţa este necesară pentru trasarea conturului, iar pensula se foloseşte la desenarea interiorului figurilor. În acest fel avem posibilitatea de a alege o peniţă cu o anumită culoare şi un anume stil şi, respectiv, o pensulă de altă culoare, desenând întreaga figură într-un singur apel de funcţie.

Clasa MFC care încapsulează obiectul GDI pensulă este CBrush. O pensulă poate fi o culoare plină, o haşură sau poate să provină dintr-un bitmap sau dintr-un şablon. Există deci constructori care primesc parametrii corespunzători acestor tipuri, fiind de asemenea posibil să creăm un obiect neiniţializat şi abia după aceea să apelăm una din multiplele funcţii de create disponibile.

Clasa CBrush este o altă clasă care ascunde un indicator HBRUSH către un obiect GDI. Acest indicator se poate determina şi utiliza în mod direct prin aplicarea unei conversii (HBRUSH) asupra unui obiect CBrush.

Pensula ce se creează cel mai uşor este pensula de culoare uniformă. Este suficient să declaraţi un obiect de tip CBrush şi să transmiteţi ca unic parametru o referinţă de culoare pentru culoarea respectivă.

CBrush brYellow(RGB(192,192,0)); În cazul pensulelor ce folosesc haşuri se vor utiliza doi parametrii, unul pentru a

indica haşura şi cel de-al doilea pentru culoare: CBrush brYellowHatch(HS_DIAGCROSS, RGB(192,192,0)); Avem la dispoziţie următoarele tipuri de haşuri: HS_CROSS - Caroiaj orizontal şi vertical HS_DIAGCROSS - Caroiaj diagonal HS_HORIZONTAL - Linii orizontale HS_VERTICAL - Linii Verticale HS_BDIAGONAL - Linii oblice (dinspre stânga sus spre dreapta jos) HS_FDIAGONAL - Linii oblice (dinspre stânga jos spre dreapta sus) Pensulele pot fi folosite pentru a modifica fundalul unei ferestre prin tratarea

mesajului WM_ERESEBKGND trimis de Windows. Pentru aceasta se procrdează astfel:

1. Se efectuează clic dreapta pe clasa CView (în exemplul dat mai înainte va avea

forma CMyDrawView) şi va fi afişat un meniu contextual. 2. Se selectează opţiunea Add Windows Message Handler

Page 99: Programare Orientata Pe Obiecte

98

3. Se selectează WM_ERESEBKGND şi se efectuează clic pe butonul Add and Edit.

4. Se modificăi funcţia OnEraseBkgnd() ca mai jos. BOOL CmyDrawView::OnEraseBkgnd(CDC *pDC) { CBrush mybr(HS_DIAGCROSS, RGB(192,192,0)); CRect rc; GetClientRect(&rc); pDC->FillRect(rc, &mybr); return TRUE; }

33..1177..1122 CCrreeaarreeaa ppeennssuulleelloorr ppee bbaazzăă ddee şşaabbllooaannee ssaauu iimmaaggiinnii Posibilitatea de a crea pensule pe baza unor imagini este o facilitate destul de

elegantă oferită de Windows. În acest fel puteţi să acoperiţi zone de ecran cu imagini dispuse alăturat.

Pentru crearea unei resurse de tip imagine bitmap se va utiliza editorul de imagini prezentat la punctul 2.5.3 al lucrării. Numele resursei de tip bitmap va fi dat de exemplu „IDB_INVADER”.

Pentru a aplica o pensulă de tip imagine vom modifica funcţia OnEraseBkgnd() în maniera următoare:

BOOL CmyDrawView::OnEraseBkgnd(CDC *pDC) { //declarăm un bitmap CBitmap bmInv; //încărcăm resursa bitmap bmInv.LoadBitmap(IDB_INVADER); //creăm o pensulă pe baza bitmap-ului CBrush brInv(&bmInv); CRect rc; GetClientRect(&rc); pDC->FillRect(rc, &brInv); return TRUE; } Windows 95 nu poate folosi decât pensule de 8x8 pixeli, dar de la Windows NT

nu avem astfel de limite.

Page 100: Programare Orientata Pe Obiecte

99

33..1177..1133 UUttiilliizzaarreeaa ppeennssuulleelloorr ddiinn ssttoocc Ca şi în cazul peniţelor există un set de pensule de stoc ce pot fi folosite în orice

moment. BLACK_BRUSH - Pensulă neagră WHITE_BRUSH - Pensulă albă DKGRAY_BRUSH - Pensulă gri închis LTGRAY_BRUSH - Pensulă gri deschis GRAY_BRUSH - Pensulă gri NULL_BRUSH -Pensulă transparentă Pentru selectarea unei pensule se poate utiliza funcţia SelectObject() a

contextului dispozitiv. Aceasta funcţionează ca şi în cazul peniţelor şi întoarce un pointer la pensula selectată anterior. Este bine să se reţină acest pointer ca şi în cazul peniţelor pentru ca la sfârşit să se selecteze pensula originală.

Asemenea peniţelor o pensulă poate fi ştearsă pentru a elibera resursa asociată, fiind posibilă apoi utilizarea uneia dintre funcţiile Create în scopul creării unei alte pensule. Funcţiile Create disponibile sunt următoarele:

CreateSolidBrush(); CreateHatchBrush(); CreateBrushIndirect(); care primeşte un parametru LOGBRUSH CreatePatternBrush(); care primeşte un bitmap CreateSysColorBrush(); Toate acestea primesc parametrii identici cu funcţiile constructor

corespunzătoare

33..1177..1144 DDeesseennaarreeaa ddee ffiigguurrii uummpplluuttee ccuu aajjuuttoorruull ppeennssuulleelloorr O mulţime de funcţii de desenare generează figuri umplute cu ajutorul

pensulelor. Pot fi desenate mai multe tipuri de poligoane, sectoare de cerc sau elipsă şi dreptunghi.

33..1177..1155 DDeesseennaarreeaa ddee ddrreeppttuunngghhiiuurrii şşii ddrreeppttuunngghhiiuurrii rroottuunnjjiittee Figurile rectangulare pot fi desenate cu ajutorul funcţiilor Rectangle() şi

RoundRect(). Rectangle() primeşte parametrii identici cu Ellipse(), astfel primeşte un dreptunghi sau patru întregi ce specifică coordonate colţurilor stânga-sus şi dreapta-jos.

Roundrect() necesită încă o pereche de coordonate primită fie ca obiect CPoint fie prin doi întregi. Această pereche de coordonate precizează lăţimea elipsei care este desenată în fiecare colţ al dreptunghiului.

Ex. PDC->RoundRect(rcClient, CPoint(15,15));

Page 101: Programare Orientata Pe Obiecte

100

33..1177..1166 DDeesseennaarreeaa ddee cceerrccuurrii şşii eelliippssee uummpplluuttee Funcţia Ellipse() despre care am mai vorbit lucrează la fel de bine şi cu

pensulele. Dacă a fost selectată o pensulă nulă va fi împiedecată generarea interiorului. De multe ori veţi dori să desenaţi elipse având diametre şi centru bine

determinate. O soluţie rapidă este să creaţi un obiect CRect() folosind pe post de coordonate stânga-sus şi dreapta-jos acelaşi obiect CPoint corespunzător centrului dorit.

Ex: CRect rcEllipse(ptCentru, ptCentru); Apoi veţi folosi funcţia InflateRect() pentru a stabili diametrele elipsei şi veţi

transmite dreptunghiul obţinut în apelul funcţiei Ellipse().

33..1177..1177 DDeesseennaarreeaa ddee ppoolliiggooaannee Funcţia Polygon() primeşte acelaşi tip de parametrii ca şi funcţiile Polyline() şi

PolyBezier(). Estenecesară o mulţime de perechi de coordonate pentru fiecare punct, şi de numărul acestor puncte.

Page 102: Programare Orientata Pe Obiecte

101

4 LUCRĂRI DE LABORATOR 4

4.1 Lucrarea de laborator 1.

44..11..11 SSiisstteemmuull ddee iinnttrrăărrii//iieeşşiirrii ddee bbaazzăă ddiinn CC++++

În C++ între intrările şi ieşirile fizice se interpun zone tampon organizate

sub forma unor clase ierarhizate, aceste zone tampon se numesc streamuri. Spre deosebire de zonele tampon din C care erau doar nişte locaţii de memorie asupra cărora acţionau o serie de funcţii în mod explicit sau implicit (printf, scanf) în C++ acestea au devenit clase, având asociate şi doi operatori unul de inserţie (<<) şi altul de extracţie (>>) care pot fi supraîncărcaţi.

În principiu sunt definite 4 streamuri standard: cin - pentru intrări standard de la Tastatură; cout – pentru ieşiri standard spre display (sau fereastră); cerr – pentru ieşiri standard de eroare spre display;

clog – ieşirile de eroare sunt păstrate trec printr-o memorie tampon înainte de ajunge la display;

În continuare se vor expune funcţionarea streamurilor cin şi cout. Biblioteca în care sunt descrise arhetipurile acestor streamuri şi care trebuie încarcată cu #include este

<iostream.h>. Avantajul utilizării streamurilor este folosirea directă fără specificări sau

configurări speciale luându-se în acest caz setările implicite sau setări făcute anterior. Deşi atunci când se va dori o anumită formatare se va apela la funcţii şi constante suplimentare spre deosebire de printf şi scanf aceste formatari se vor păstra, astfel dacă se doreşte ca afişările de la un punct la alt punct al programului să se facă cu aceeaşi formatare se va specifica formatarea o singura data, apoi aceasta păstrându-se.

Pentru a afişa nişte variabile,constante, şiruri de caractere pe ecran vom scrie: cout<<v1<<v2<<k1<<”abcdefg”<<’a’<<’*’; Se vor afişa una după alta fără spaţii între ele valoare lui v1, a lui v2 a constantei

k1, şirul de caractere dintre ghilimele, caracterul ‘a’ şi ’*’. Pentru a citi o variabilă sau un şir de caractere vom scrie:

Page 103: Programare Orientata Pe Obiecte

102

int a; char s[100]; cin>>a; cin>>s; sau … cin>>a>>s; Se observă că săgeţile indică întotdeauna sensul de deplasare a informaţiei la

cout toate informaţiile se deplasează dinspre variabile spre stream, pe când la cin dinspre stream spre variabile.

Exerciţiu 1: Citiţi trei valori float, calculaţi media lor şi afişaţi-o.

44..11..22 FFoorrmmaattaarreeaa ssttrreeaammuurriilloorr

Formatarea afişărilor şi intrărilor se poate face cu o serie de indicatori de format şi o serie de funcţii prin care se activează aceşti indicatori.

Indicatorii de format sunt efectiv valori întregi pe 16 biţi. Aceste valori în format binar au doar un singur bit setat, permiţând astfel implementarea a 16 indicatori maxim pe acest principiu. Ca tip de date aceşti indicatori sunt definiţi ca enumerare.

Aceştia sunt definiţi astfel: Enum{ skipws=0x0001, left=0x0002, right=0x0004, internal=0x0008, dec=0x0010, oct=0x0020, hex=0x0040,showbase=0x0080, showpoint=0x0100, uppercase=0x0200, showpos=0x0400, scientific=0x800,fixed=0x1000,unitbuf=0x2000 }; Pentru setarea acestor indicatori se foloseşte funcţia membru

setf(indicator), Pentru resetarea lor se foloseşte funcţia unsetf(indicator). Pentru a seta indicatorul hex scriem: stream.setf (ios::hex); unde stream poate fi cin sau cout. Pentru a seta mai mulţi indicatori vom folosi următoare secvenţă de cod: stream.setf(ios::ind1| ios::ind2 | ios::ind3 | …. ios ::indn) ; Pentru a reseta indicatorii de format ai unui stream folosim aceleaşi

expresii dar în loc de setf vom scrie unsetf. Semnificaţiile indicatorilor de format: - skipws – la citirea unui şir de caractere nu ia în considerare spaţiile goale

(spaţii şi tabulatori);

Page 104: Programare Orientata Pe Obiecte

103

Exemplu: char s[100]; cin.setf(ios::skipws); cin>>s; cout<<s;

- left – alinează afişarea pe ecran la stânga; ( abc______) - right – alinează afişarea pe ecran la dreapta;(______abc) - internal – alinează semnul valorii la stânga;(-_____123) - dec – specifică că streamul va lucra cu valori în baza 10; - oct – specifică că streamul va lucra cu valori în baza 8; - hex – specifică că streamul va lucra cu valori în baza 16; - showbase –specifică că se va afişa baza de numeraţie în care se lucrează (

01234 pentru octal, 0xFFA9 pentru hexazecimal, fără nici o specificaţie pentru zecimal);

- showpoint – arată punctul zecimal pentru valori reale chiar şi atunci când nu au zecimale diferite de zero (1234.0000)

- uppercase – arată notaţia bazelor cu literă mare şi pentru baza 16 toate cifrele literare (a,b,c,d,e,f) vor fi scrise cu literă mare;(FFB9)

- showpos – dispune în faţa valorilor pozitive semnul +. ( +1234); - scientific – valorile reale vor fi afişate în notaţie ştiinţifică (1.234e+03); - fixed – valorile reale vor fi afişate în notaţie cu virgulă, opţiune ce este

implicită; - unitbuf – se golesţe memoria tampon asociată streamurilor după fiecare

operaţie de ieşire;

Exerciţiu 2. Să se citească un număr întreg în baza 16 şi să se afişeze valoare citită în

baza 8. Să se active afişarea simbolizării bazei.

Exerciţiu 3.

Să se citească o valoare float şi să se afişeze fără punctul zecimal.(dacă valoarea nu are zecimale), altădată să se afişeze în notaţie ştiinţifică, şi mai apoi în notaţie fixă. Alte funcţii membre ale streamurilor: flags(), width(), precision(), fill() Pentru a citi sau a scrie valorile tuturor indicatorilor de formatare se va folosi

funcţia flags(). Exemplu: long a; a=flags(); // am citit toate valorile flags(a); // am suprascris toate valorile

Page 105: Programare Orientata Pe Obiecte

104

Pentru a specifica lungimea minima a spaţiului(câmpului) de afişare a unei variabile, şir de caractere sau constante se foloseşte funcţia width() având prototipul:

int width(int dimensiune_camp); Pentru a specifica precizia(numărul de zecimale) cu care va fi afişat un număr

real se fa folosi funcţia membru precision() având prototipul: int precision(int nr_de_zecimal); Pentru a specifica caracterul cu care se umple spaţiile libere ale unui câmp când

informaţia efectivă afişată ocupă mai puţin decât lărgimea câmpului se foloseşte fill() având prototipul:

char fill(char noul_caracter_de_umplere); // funcţia va returna caracterul de umplere anterior Exerciţiu 4. Să se afişeze pe câte un rând valoare radicalului primelor 15 numere începând cu

2. Valoarea va fi afişată cu 7 zecimale. Pe prima coloană se va afişa numărul, pe cea de-a doua radicalul său. Fiecare câmp va avea o lărgime de 20 de caractere iar valorile vor fi alineate la dreapta.(Ca în figura de mai jos.)(pentru radical se va include biblioteca math.h şi din această bibliotecă se va folosi funcţia sqrt(), pentru linie nouă se va folosi caracterul `\n`) Este setarea făcută cu funcţia width() permanentă?

44..11..33 UUttiilliizzaarreeaa mmaanniippuullaattoorriilloorr ppeennttrruu ffoorrmmaattaarreeaa iieeşşiirriilloorr Manipulatorii sunt o serie funcţii speciale declarate în biblioteca IOMANIP.H. Aceştia sunt : - dec, oct, hex – pentru a specifica baza de numeraţie a intrărilor şi ieşirilor;

Page 106: Programare Orientata Pe Obiecte

105

- setbase(int baza) – stabileşte baza valorilor numerice ce intră sau ies din stream;

- endl – pentru un caracter de linie nouă (echivalentul unui Enter din tastatură);

- ends – scrie în stream un caracter null (0x00 sau `\0`); - ws – pentru streamuri de intrare emite spaţiile libere introduse înainte de

valoarea efectivă; - flush – goleşte un stream; - resetiosflags(long f) – dezactivează indicatorii specificaţi în f; - setiosflags(long f) – activează indicatorii specificaţi f; - setprecision(int precizie) – stabileşte numărul de zecimale al valorilor cu

virgulă ; - setfill(int ch) – stabileşte caracterul de umplere; - setw(int w) – stabileşte lărgimea câmpului în care se va afişa o valoare sau

şir de caractere; Pentru a folosi un manipulator vom folosi expresia: cout<<manip1<<v1<<…<<manip2<<manip3<<vk…; cin>>manip1>>v1>>manip2>>…;

Exerciţiul 5. Să se afişeze pe câte un rând,în câmpuri de 20 de caractere,alineate la

dreapta, valoarea lui radical din 2, mai întâi cu o zecimală şi apoi crescând cu unu numărul de zecimale. Vor fi afişate astfel zece rânduri. Se va da prioritate folosirii manipulatorilor mai sus prezentaţi.(Rezultatul programului va fi cel din figura de mai jos.)

Definirea propriilor manipulatori Se pot crea manipulatori proprii care sa acţioneze asupra unor streamuri. Pentru a crea un manipulator se poate utiliza această structură general

valabilă: Pentru manipulatori folosiţi împreună cu cout:

Page 107: Programare Orientata Pe Obiecte

106

ostream & nume_manipulator_out (ostream & stream_de_lucru) { // se fac operatii cu streamul stream_de_lucru<<…<<…; stream_de_lucru.setf() ; stream_de_lucru.precision() ; stream_de_lucru<<endl<<ends; // aceste operatii sunt date ca exemplu putandu-se alege

oricare // combinatie ce este utila return stream_de_lucru ;//aceasta comanda este absolut

necesara // este ultima comanda din corpul functiei //de definire

} Pentru manipulatori folosiţi împreună cu cin: istream & nume_manipulator_in (istream & stream_de_lucru)

{ // se fac operatii cu streamul stream_de_lucru>>…>>…; stream_de_lucru.setf() ; stream_de_lucru.precision() ; stream_de_lucru>>ws; // aceste operatii sunt date ca exemplu putandu-se alege

oricare // combinatie ce este utila return stream_de_lucru ;//aceasta comanda este absolut

necesara // este ultima comanda din corpul functiei //de definire

} Pentru a folosi aceşti manipulatori se va folosi secventa: cout<<…<<nume_manipulator_out<<…; cin<<…<<nume_manipulator_in<<…; Exerciţiu 6 Creaţi un manipulator de ieşire cu numele rand_nou, care atasat unui

stream cout sa facă acelaşi lucru ca manipulatorul endl. Sau un manipulator da_un_bip

Page 108: Programare Orientata Pe Obiecte

107

care atasat lui cout să scoată un sunet scurt de avertizare. (Se va folosi caracterul special ‘\a’).

Exerciţiu 7 Creaţi un manipulator de intrare cu numele cere_parola, care ataşat unui

stream cin să ceară introducerea unui şir de caractere ce semnifică parola. Dacă parola este greşită se va emite un sunet de avertizare folosindu-se manipulatorul da_un_bip creat mai înainte şi se va repeta secvenţa de solicitare a parolei. Altfel se va confirma că parala este corectă şi se va încheia programul.( Pentru compararea şirului introdus cu parola dorită se va folosi funcţia strcmp(sir1,sir2) din biblioteca string.h).

5

Page 109: Programare Orientata Pe Obiecte

108

4.2 Lucrarea de laborator 2.

44..22..11 CCrreeaarreeaa ccllaasseelloorr şşii lluuccrruull ccuu oobbiieeccttee îînn CC++++ Organizarea programelor sub forma unor înlănţuiri de clase reprezintă următorul

pas după ce s-a deprins lucrul cu funcţiile şi construcţia structurilor de date. Pentru cine este obişnuit cu acestea, definirea şi lucrul cu clase li se va părea ca şi cum ar avea o structură de date (struct) căreia i s-au asociat o serie de funcţii. Dar asemănările din păcate se opresc aici, deşi imaginea simplificatoare este în mare parte valabilă. Odată cu avantajele utilizării claselor apar şi unele restricţii. Funcţiile definite în clasă (care poartă numele de funcţii membre ale clasei) au acces preferenţial asupra datelor definite în interiorul clasei şi uneori pot fi chiar singurele funcţii ce au acces la aceste date. Pe de altă parte datele din clasă sunt de cele mai multe ori inaccesibile din afară,doar funcţiile membre le pot citi şi modifica. Important de reţinut sunt avantajele pe care le aduce folosirea claselor :

Încapsularea, Polimorfismul şi Moştenirea. Încapsularea înseamnă că datele clasei nu sunt accesibile din afară ci mai

întotdeauna sunt accesate prin medierea funcţiilor membre.(nu înseamnă că chiar nu se pot accesa datele ci doar că clasele au fost concepute să funcţioneze astfel).

Polimorfismul înseamnă că putem defini o funcţie de mai multe ori, astfel încât să accepte diferite combinaţii de parametri şi să realizeze aceeaşi sarcină folosind parametri diferiţi dar specificaţi.(funcţia va avea acelaşi nume dar va diferi numărul şi tipul parametrilor la fiecare declarare).

Moştenirea presupune că caracteristicile unei clase – datele şi funcţiile – sunt puse la dispoziţia sau mai degrabă devin baza pe care se construieşte altă clasă. Astfel clasa nou creată – clasa fiu – va moşteni elementele clasei tată. Se pot crea clase prin moşteniri succesive, sau prin moştenirea mai multor clase deodată. Clasa fiu va putea defini noi funcţii şi date în completarea celor moştenite.

Definirea unei clase se face astfel: class nume_clasa{ tip1 data1; tip2 data1[10]; … tipn datan; specificator1: nume_clasa(…); ~nume_clasa(…); tip1 functie1(…); specificator2: tip2 functie2(…); … tipn functien(…):

} [obiect1],[obiect2…];

Page 110: Programare Orientata Pe Obiecte

109

Aceasta este structura generală de definire a unei clase. Se observă cuvântul cheie class de la început, acesta se va regăsi în toate definiţiile de clase.

Specificator1…n sunt specificatori de acces care stabilesc accesul la membrii clasei cei care se găsesc după el fiind afectaţi, până ce apare un nou specificator de acces.

nume_clasa(…) este o funcţie constructor a clasei, se apelează automat când se declară un obiect de clasa respectivă.

~nume_clasa(…) este o funcţie destructor a clasei, se apelează când s-a terminat lucrul cu obiectul definit, pentru a elibera memoria.

Tipk functiek(…) este o funcţie membru al clasei. După ce s-a definit clasa trebuie definite şi funcţiile membre. În general pentru a

accesa orice membru al unei clase pentru definire se foloseşte operatorul de specificare a domeniului :: . Exemplu:

tipk nume_clasa::functiek(…) { // scriem continutul functiei return tipk; } În definiţia clasei observăm că opţional se pot defini variabile de tipul clasă

numite obiecte. Pentru a accesa membrii clasei atunci când avem declarat un obiect vom folosi operatorul de selecţie . (punct) atunci când lucrăm cu obiectul sau operatorul de selecţie indirectă -> (minus, mai mare) atunci când lucrăm cu pointer spre obiect. Astfel:

nume_clasa obiect1,obiect2,* obiect3; obiect1.functiek(…); obiect2.functiek(…); // dar… obiect3->functiek(…); Specificatorii de acces tipici sunt private (setat implicit pentru toţi membrii unei

clase), public şi protected. Primii doi în special public sunt cei mai utilizaţi. Private

interzice accesul oricărui nemembru al clasei. Public permite accesul oricui la membrii clasei. Protected specifică că membrii declaraţi protected în clasa tată vor putea fi accesaţi în cazul unei moşteniri fără specificatori de către clasa fiu. Pentru a avea acces fără protected la membrii clasei tată la moştenire clasa tată va fi moştenită de clasa fiu în mod public.

Pentru a specifica că o nouă clasă moşteneşte pe altele vom folosi structura: Class clasa_fiu:[specificator1] clasa_tata1[,[specificator 2]

clasa_tata2,…] { //noi elemente specifice clasa_fiu };

Page 111: Programare Orientata Pe Obiecte

110

Astfel o clasa_fiu poate moşteni una sau mai multe clasa_tata, moştenirea fiind filtrată prin specificatorii de acces ce precedă clasa_tata moştenită. (Este bine ca aceşti specificatori să fie – public - pentru ca accesul clasei fiu să fie permis la toţi membrii clasei tată indiferent de specificatorii din aceasta clasa_tata, aceasta deoarece specificatorul implicit este private în cazul unei moşteniri fără precizarea specificatorului).

Exerciţiu 1. Definiţi o clasa cu numele persoana, cu următoarele date, nume,adresa,vârstă,

cnp.Definiţi o funcţie constructor şi una destructor. Definiţi funcţiile spune_nume, spune_adresa, spune_varsta , spune_cnp care să citească de la tastatura datele respective. Definiţi alte funcţii care sa returneze aceste date ,separat pentru fiecare dată în parte.(ex. da_nume(char * numecurent)). Apoi funcţii de transfer a datelor de la un obiect la altul. (trans_nume(persoana *altpers)). Nu folosiţi deocamdată nici un specificator de acces. Încercaţi să modificaţi numele unei persoane prin accesarea directă a datei respective. Se poate?.Ce trebuie schimbat dacă nu?. La fel încercaţi să folosiţi o funcţie membră a clasei fără a avea un specificator de acces în clasă. Cum trebuie modificat programul pentru ca accesul la datele membre să fie interzis şi accesul la funcţii membre permis? Încercaţi să creaţi o clasă prin moştenirea clasei persoana , anume clasa angajat, care să aibă suplimentar următoarele date: nume_firma,salariu_net, data_angajarii(de tip char în format zz-ll-aaaa). Creaţi şi pentru aceasta clasă funcţii de citire a datelor noi, de returnare a datelor noi, de transfer de date. Faceţi moştenirea fără precizarea unui specificator de acces. Vă este permis accesul direct la numele persoanei din clasa angajat ? Ce trebuie modificat ?(Aveţi două variante…) . Scrieţi o funcţie externă claselor pentru a afişa informaţii despre angajat. Aveţi acces la membrii clasei? Pentru a rezolva problema în mod profesionist puteţi apela la funcţii prietene. Citiţi rândurile de mai jos şi rezolvaţi problema.

Folosirea funcţiilor prietene. Având declarată deja o funcţie, pentru a permite accesul la membrii unei clase o

vom declara în interiorul clasei ca fiind funcţie prietenă cu specificatorul friend. Acesta va fi folosit astfel:

class nume_clasa{

…// declaram date membre …// declaram functii membre

friend tip_returnat functie_prietena(…); // functia a devenit prietena a

//clasei }; …

//se observa ca definitia functiei nu se schimba deloc tip_returnat functie_prietena(…) { };

Page 112: Programare Orientata Pe Obiecte

111

Astfel indiferent de ce specificatori de acces au membrii clasei, funcţia prietenă va avea acces neîngrădit la toţi membrii clasei.

Se pot defini şi clase prietene unei clase nu numai funcţii prietene, cam în acelaşi mod.

class nume_clasa{ …// declaratii member friend class nume_clasa_prietena;

}

Astfel nume_clasa_prietena are acces la toţi membrii nume_clasa, dar nume_clasa nu are acces neapărat la toţi membrii clasei nume_clasa_prietena. Deci reciproca nu este valabilă.(decât specificat în mod expres…)

Când lucrăm cu pointeri la obiect, iar obiectului respectiv nu i s-a alocat

memorie, va trebui ca înainte de folosire să folosim operatorul de alocare new, iar când nu mai lucrăm cu obiectul operatorul delete. Ei au următoarea adresare:

nume_obiect=new nume_clasa; delete nume_obiect; Putem folosi aceşti operatori şi cu alte tipuri decât clase. În loc de nume_obiect

având nume_variabilă şi în loc de nume_clasa numele tipului. Când avem definiţi constructori apelarea acestora se va face în momentul

alocării,respectiv destructori la apelul operatorului delete. Până acum nu s-a pus problema constructorilor şi destructorilor cu parametri.

Aceştia sunt declaraţi şi definiţi în modul în care sunt definite funcţiile desigur fără a returna un tip. În schimb la declararea obiectelor va trebui specificat între paranteze după numele obiectului fiecare parametru specificat în prototip.

nume_clasa obiect(p1,p2,p3…); Pentru obiect alocate dinamic : obiect = new nume_clasa(p1,p2,p3…); Exerciţiu 2. Modificaţi programul anterior astfel încât obiectele definite să fie

alocate dinamic. Pentru a ne referi strict la o instanţă a unui membru al clasei, adică acea

declarată şi folosită într-un obiect anume, pentru a avea acces la datele şi funcţiile din memoria alocată acestuia vom folosi pointerul this (trad. acesta). Acesta returnează adresa

obiectului. Pentru a ne referi la un membru al clasei din interiorul unei funcţii membru vom folosi apelare:

Page 113: Programare Orientata Pe Obiecte

112

this->membru=…; this->functie_membru(); …=this->membru; Un alt element al programării pe obiecte este utilizarea funcţiilor virtuale.

Declararea virtuală a unei funcţii are utilitate în procesul de moştenire (sau derivare). Astfel o funcţie declarată virtuală în clasa de bază este moştenită ca virtuală în clasele derivate. Cu toate că avem funcţia definită în clasa de bază dacă ea este redefinită în clasa derivată această funcţie va face când va fi apelată ceea ce s-a precizat în clasa derivată şi nu ceea ce făcea în clasa de bază. Deci faptul că o funcţie este virtuală permite suprascrierea ei în toate clasele ce vor rezulta prin derivarea clasei de bază (prin utilizarea aceluiaşi nume).

Exemplu: class animal_inferior{ …//caracteristici generale specifice oricarui animal inferior virtual void mod_de_deplasare() {cout<<”Inoata!”<<endl;}

}; class peste:public animal_inferior{ …//caracterisitici specifice // nu redeclaram functia pt ca este valabila afirmatia „Inoata!”

}; class patruped:public animal_inferior{ void mod_de_deplasare(){cout<<”Merge!” <<endl;;}

}; class pasare: public animal_inferior{ void mod_de_deplasare(){cout<<”Zboara!” <<endl;;}

}; … void main() { animal_inferior meduza; peste rechin; patruped elefant; pasare vultur; … animal_inferior. mod_de_deplasare(); elefant. mod_de_deplasare(); rechin. mod_de_deplasare(); vultur. mod_de_deplasare(); } Programul va afişa: Inoata! Merge! Inoata! Zboara!

Page 114: Programare Orientata Pe Obiecte

113

Se observă că-n toate clasele derivate în afară de peste funcţia

mod_de_deplasare() a fost suprascrisă. Exerciţiu 3. Creaţi modificând exemplul de mai sus o clasă pinguin derivată din

pasare, iar în această clasă modificaţi funcţia virtuală astfel încât să afişeze „paseste si inoata”.

Declaraţi un obiect de tip pinguin şi verificaţi dacă funcţia virtuală a fost suprascrisă şi programul funcţionează corect.(De remarcat că în toate celelalte clase derivate funcţia declarată virtual în clasa de bază nu mai trebuie precedată de virtual, acest atribut fiind subînţeles.) Obs. Se pare că programul nu funcţionează. Problema apare pentru că la suprascrierea funcţiilor virtuale se suprascrie şi specificatorul de acces, şi pentru că nu este precizat este luat implicit private. Asta înseamnă că accesul la funcţia membru mod_de_deplasare nu vă este permis. Rezolvaţi problema… După ce aţi rezolvat problema adăugaţi un constructor clasei de bază şi prin acesta transmiteţi un şir de caractere cu numele efectiv al speciei animalului pe care îl păstraţi într-un şir de caractere.

Aceste nume îl veţi afişa înainte de a spune cum se deplasează animalul făcând o modificare în funcţiile virtuale din fiecare clasă preferabil la aceeaşi apelare a cout.

( pentru copierea şirului de caractere în alt şir folosiţi funcţia strcpy(dest,sursa) din biblioteca string.h). Veţi constata erori deoarece constructorii nu se moştenesc în mod implicit. Chiar şi derivarea trebuie din nou specificată pentru constructori în mod obligatoriu.(sintaxa este nume_clasa(char *numeanimal):clasa_baza(numeanimal){};

şi redefinirea se face pentru fiecare clasă derivată. ). Trebuie reţinut că la rularea unui program mai întâi sunt apelaţi în ordinea derivării mai întâi constructorul clasei de baza, apoi cei ai clasei derivate pe urmă cei ai clasei derivate din aceasta ş.a.m.d., ultimul apelat fiind cel al clasei din care face parte obiectul. Pentru destructori aceeaşi regulă numai că ordinea este inversată.

Page 115: Programare Orientata Pe Obiecte

114

4.3 Lucrarea de laborator 3.

44..33..11 VViissuuaall CC++++66..00 –– MMFFCC DDeesseennaarreeaa şşii aaffiişşaarreeaa iimmaaggiinniilloorr

Utilizarea documentelor, a reprezentărilor şi a cadrelor Pentru programatorul în VisualC++, biblioteca MFC oferă un sprijin

substanţial la generarea unei noi aplicaţii prin intermediul AppWizard, care poate să creeze automat un meniu, o bară de instrumente, o bară de stare şi alte componente, permiţând apoi personalizarea uşoară a fiecărui element. Clasele create cu AppWizard lucrează împreună pentru a forma o structură omogenă cunoscută sub numele de arhitectura Document/View.

Conceptul fundamental al arhitecturii Document/View îl reprezintă separarea datelor propriu-zise de reprezentarea a ceea ce datele semnifică pentru utilizator. Aceasta se realizează stocând datele într-o clasă (clasa document) şi informaţiile privind reprezentarea într-o altă clasă (clasa vizualizare).

Există două categorii de aplicaţii Document/View: SDI (Single Document Interface) şi MDI (Multiple Document Interface).

Exerciţiu 1. Să se creeze o aplicaţie de tip SDI cu AppWizard . Aplicaţia pe care o veţi dezvolta va afişa un teanc de monede. Opţiunile de

meniu vor permite adăugarea sau înlăturarea unei monede din teanc. Datele, care reprezintă de fapt numărul de monede, sunt reţinute în clasa document, fiind accesate de clasa vizualizare în scopul afişării teancului sau de clasa cadru pentru actualizare. Deşi este un exemplu simplu, el oferă o imagine asupra scopului fundamental al arhitecturii Document/View, şi anume încapsularea datelor. Prin încapsulare, datele sunt stocate exclusiv în clasa document, fiind oferite funcţii de acces care să permită clasei vizualizare sau clasei cadru să prezinte informaţiile către utilizator, respectiv să permită actualizarea acestor informaţii.

Folosind opţiunea AddMemberVariabile adăugaţi o variabilă protected

cu numele m_nNrMonede clasei document a proiectului. O variabilă protejată nu poate fi modificată decât prin intermediul funcţiilor

membru ale clasei din care face parte sau ale unei clase derivate. Prevenind alterarea datelor documentului de către oricare altă clasă, rămâne un singur punct de modificare a unei variabile. Pentru a permite accesul la datele membre, clasa document trebuie să ofere funcţii de acces sau de modificare a variabilelor membre.

. Folosind opţiunea AddMemberFunction adăugaţi trei metode clasei

document: - o metodă int GetNr() care să returneze valoarea datei membre m_nNrMonede,

Page 116: Programare Orientata Pe Obiecte

115

- două metode void Inc() şi void Dec() pentru incrementarea respectiv decrementarea valorii variabilei m_nNrMonede.

Pentru ca reprezentarea să poată extrage date ale documentului, ea trebuie să fie

mai întâi capabilă să acceseze obiectul document. Infrastructura MFC se ocupă automat de acest aspect, adăugând în clasa reprezentare a aplicaţiei metoda GetDocument.

Clasa derivată din CView este responsabilă de afişarea teancului de monede. Codul care se ocupă efectiv de desenarea monedelor se află în funcţia OnDraw()a clasei respective. AppWizard creează un schelet al acestei funcţii, care trebuie apoi completat:

void CSDIView::OnDraw(){ //Obtinerea pointerului la document CSDIDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //Salvarea pensulei curente CBrush* pOldBrush=pDC->GetCurrentBrush(); //Crearea unei noi pensule de culoare galbena CBrush br; br.CreateSolidBrush(RGB(255,255,0)); //Selectarea pensulei galbene in contextul dispozitiv pDC->SelectObject(&br); //Obtinerea numarului de monede de la document si //reprezentarea fiecarei monede prin doua elipse for(int i=0;i<pDoc->GetNr();i++){ int y=200-30*i; pDC->Ellipse(40,y,100,y-30); pDC->Ellipse(40,y-5,100,y-35); } //Restaurarea vechii pensule pDC->SelectObject(pOldBrush); }

Exerciţiu 2. Adăugaţi două opţiuni de meniu Add şi Rem pentru adăugarea şi eliminarea unei monede. Trataţi aceste resurse de meniu în clasa CMainFrame:

void CMainFrame::OnAdd() {

//Obţinerea pointerului spre document

CSDIDoc* pDoc=(CSDIDoc*)GetActiveDocument();

pDoc->Add();

//Actualizarea vizualizării documentului

pDoc->UpdateAllViews(NULL);

}

void CMainFrame::OnRem() {

Page 117: Programare Orientata Pe Obiecte

116

//Obţinerea pointerului spre document

CSDIDoc* pDoc=(CSDIDoc*)GetActiveDocument();

pDoc->Rem();

//Actualizarea vizualizării documentului

pDoc->UpdateAllViews(NULL);

}

Acum compilaţi şi executaţi .

44..33..22 DDeesseennaarreeaa şşii aaffiişşaarreeaa iimmaaggiinniilloorr ffoolloossiinndd ccoonntteexxttee ddee ddiissppoozziittiivv

Un context dispozitiv reprezintă suprafaţa pe care se desenează toate

punctele, liniile, pătratele, fonturile, culorile. Cuvântul “dispozitiv” din context dispozitiv înseamnă că se poate desena pe ecran, la imprimantă, pe un plotter fără a cunoaşte prea multe detalii despre ce dispozitiv se foloseşte sau ce marcă sau model este acesta.

Există un context dispozitiv standard brut, şi există contexte dispozitiv pentru situaţii speciale şi operaţii particulare. MFC oferă încapsulări ale contextelor dispozitiv care simplifică interacţiunea cu obiectele GDI aflate dedesubt. Clasa care încapsulează contextul dispozitiv standard brut este CDC. Această clasă conţine un număr mare de funcţii de desenare, de mapare de coordonate şi de decupare pentru implementarea reprezentărilor grafice. Toate celelalte clase de context dispozitiv, mai specializate, sunt bazate pe această clasă şi o extind.

Capacitatea clasei CDC de a se ataşa şi a desena într-un context dispozitiv poate fi ilustrată printr-un program simplu. Fiecare fereastră are asociat un context dispozitiv care acoperă întreaga fereastră; nu face excepţie nici fereastra suprafeţei de lucru, care se întinde pe întregul ecran.

Aplicaţia următoare acaparează un context dispozitiv şi îl foloseşte pentru desenare.

Exerciţiu 3. Creaţi o aplicaţie de tip SDI.Adăugaţi un buton cu numele Draw resursei de meniu din cadrul proiectului. Cu ajutorul ClassWizard trataţi mesajul generat de apăsarea butonului, într-o funcţie Draw() situată în clasa CSDIView.Completaţi corpul funcţiei cu următorul cod:

void CSDIView::OnDrawIt() {

Page 118: Programare Orientata Pe Obiecte

117

//Obtinerea unui pointer la fereastra suprafatei de lucru

CWnd* pDeskTop=GetDesktopWindow();

//Obtinerea unui pointer la contextul dispozitiv al //acesteia

CDC* pDC=pDeskTop->GetWindowDC();

for(int i=0;i<300;i++)

for(int j=0;j<300;j++)

//Desenarea fiecarui pixel cu o alta culoare

pDC->SetPixel(i,j,i*j));

//Eliberarea contextului dispozitiv

pDeskTop->ReleaseDC(pDC);

}

44..33..33 UUttiilliizzaarreeaa ccoonntteexxtteelloorr ddiissppoozziittiivv cclliieenntt -- CCCClliieennttDDCC

Pentru a desena în contextul dispozitiv al ferestrei aplicaţiei şi nu în fereastra suprafeţei de lucru se foloseşte un pointer pDC de tip CClientDC. Se va folosi proiectul creat la punctul anterior, modificându-se corpul funcţiei Draw():

void CSDIView::Draw(){

// Construim un DC pentru fereastra client

CClientDC pDC(this);

for(int x=0;x<300;x++)

for(int y=0;y<300;y++)

pDC->SetPixel(x,y,x*y);

}

44..33..44 UUttiilliizzaarreeaa ccoonntteexxtteelloorr ddiissppoozziittiivv ddee rreeddeesseennaarree -- CCPPaaiinnttDDCC

Clasa CPaintDC este o încapsulare specială de context dispozitiv care ajută la tartarea mesajului WM_PAINT transmis de Windows. Mesajul WM_PAINT este transmis unei ferestre atunci când suprafaţa acesteia a fost descoperită parţial sau total de o altă fereastră.

În loc să se redeseneze întreaga fereastră de fiecare dată când este descoperită o mică porţiune, Windows transmite coordonatele unui dreptunghi care încadrează zona descoperită. Aceste informaţii se pot folosi pentru a desena exclusiv

Page 119: Programare Orientata Pe Obiecte

118

porţiunea afectată, fără a mai irosi timp pentru a desea zone de ecran pe care utilizatorul oricum nu le poate vedea.

Pentru a experimenta redesenarea se creează un nou proiect SDI şi cu ajutorul AppWizard-ului se tratează mesajul WM_PAINT în funcţia OnPaint(). Se completează această funcţie astfel:

void CSDIView::OnPaint(){

//Crearea unui context dispozitiv pentru desenare

CPaintDC paintDC(this);

//Crearea uni pointer spre dreptunghiul de redesenare

RECT* pRect=&paintDC.m_ps.rcPaint;

for(int x=pRect->left;x<pRect->right;x++)

for(int y=pRect->top;y<pRect->bottom;y++)

paintDC.SetPixel(x,y,x*y);

}

Compilaţi şi rulaţi aplicaţia.

La prima afişare a ferestrei este transmis un mesaj WM_PAINT pentru redesenarea întregii suprafeţe. Pentru a vedea efectul mesajului de redesenare, acoperiţi fereastra aplicaţiei cu o altă fereastră. Deplasaţi apoi această fereastră până când nu se va mai suprapune peste fereastra aplicaţiei. Se va observa că redesenarea se va face mai repede deoarece regiunea acoperită devine din ce în ce mai mică, fiind astfel necesare mai puţine apeluri SetPixel.

44..33..55 CCoonntteexxttee ddiissppoozziittiivv ddee mmeemmoorriiee -- CCDDCC Un context dispozitiv de memorie este un context dispozitiv care nu are

asociat nici un dispozitiv. Aceste contexte dispozitiv se folosesc de regulă împreună cu un context dispozitiv obişnuit pentru copierea şi lipirea unor zone de ecran. Este posibilă crearea unui context dispozitiv de memorie compatibil cu un dispozitiv de afişare. Apoi se pot copia în memorie imaginile care nu mai sunt afişate, aducându-le înapoi în contextul dispozitiv de afişare atunci când este nevoie.

Exerciţiu 4.Creaţi o aplicaţie de tip SDI. Adăugaţi un buton cu numele Draw resursei de meniu din cadrul proiectului. Cu ajutorul ClassWizard trataţi mesajul generat

Page 120: Programare Orientata Pe Obiecte

119

de apăsarea butonului într-o funcţie Draw() situată în clasa CSDIView. Completaţi corpul funcţiei cu următorul cod:

void CDCDrawDlg::OnDrawIt(){

//Construim un DC client pentru fereastra dialog

CClientDC clientDC(this);

//Creem un context dispozitiv de memorie care să fie //compatibil cu un context dispozitiv de pe ecran

CDC memDC;

memDC.CreateCompatibleDC(&clienDC);

//Determinăm zona client

CRect rcClient;

GetClientRect(&rcClient);

//Creem un bitmap compatibil cu atributele contextului //dispozitiv de pe ecran

CBitmap memBitmap;

memBitmap.CreateCompatibleBitmap(&ClientDC, rcClient.Width(),rcClient.Height());

//Îl selectăm în cadrul contextului dispozitiv de memorie

memDC.SelectObject(&memBitmap);

//Parcurgem dreptunghiul de desenare pe orizontală

for(int x=0; x<rcClient.Width(); x++)

{

// Parcurgem dreptunghiul de desenare pe verticală

for(int y=0; y<rcClient.Height(); y++)

{

//Desenăm fiecare pixel cu altă culoare; //desenarea are loc în memorie, în acest moment //nu se vede nimic

memDC.SetPixel(x,y,x*y);

}

}

//Copiem imaginea din memorie înapoi în contextul //dispozitiv client; întreaga imagine este copiată acum pe //ecran, devenind vizibilă utilizatorului

clientDC.BitBlt(0,0, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY);

Page 121: Programare Orientata Pe Obiecte

120

}

}

Compilaţi şi executaţi .

Page 122: Programare Orientata Pe Obiecte

121

4.4 Lucrarea de laborator 4.

Visual C++ 6.0 –MFC Bare de control. Dialoguri comune. Foi de proprietăţi.

44..44..11 BBaarree ddee ccoonnttrrooll Barele de control sunt elemente de interfaţă cu utilizatorul, folosite pentru a

conţine alte controale sau alte ferestre. Există trei categorii de bare de control: Barele de stare - reprezintă cel mai simplu tip de bare de control. Barele de

control sunt în permanenţă afişate în partea de jos a unei ferestre cadru. Bare de instrumente - conţin butoane folosite drept comenzi rapide de meniu. Bare de dialog - conţin butoane şi alte categorii de controale, cum ar fi casetele

combinate, casetele listă sau controalele de desfăşurare.

44..44..22 BBaarree ddee ssttaarree Barele de stare sunt un element standard al interfeţei cu utilizatorul. O bară de

stare este o bară de control aflată în partea de jos a cadrului unei aplicaţii. Barele de stare sunt, de obicei, divizate în mai multe panouri, cunoscute şi sub numele de indicatori. De exemplu, aplicaţiile create cu AppWizard are panouri destinate afişării stării tastelor Num Lock şi Caps Lock.

Într-un program MFC, toate barele de control aparţin ferestrei principale. Clasa cadru principală, CMainFrame, conţine un obiect CStatusBar denumit m_wndStatusBar care este creat şi iniţializat în funcţia CMainFrame::OnCreate().

if(!m_wndStatusBar.Create(this)||

!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0(“Nu s-a reusit crearea unei bare de stare

\n”) return –1; } Înainte de a crea o bară de stare, trebuie creată o matrice de identificatori de

resursă, folosiţi pentru fiecare panou al barei de stare. Această matrice de identificatori este transmisă ca parametru funcţiei SetIndicators(). Identificatorii se folosesc pentru a identifica fiecare panou al barei de stare şi şirul text prestabilit pentru fiecare panou în parte. Este necesară atribuirea unei resurse şir prestabilite pentru fiecare panou adăugat barei de stare.

Proprietăţile unui panou din bara de stare se stabilesc apelând funcţia SetPaneInfo.

m_wndStatusbar.SetPaneInfo(4, ID_INDICATOR_TIME,

SBPS_POPOUT, 80);

Page 123: Programare Orientata Pe Obiecte

122

Funcţia SetPaneInfo primeşte ca parametrii: indexul panoului, identificatorul panoului, stilul panoului şi lăţimea acestuia. Stilurile de panou disponibile sunt:

- SBPS_STRECH - arată că acel panou se poate extinde pentru a acoperi spaţiul

nefolosit. Un singur panou dintr-o bară de stare poate avea acest atribut, App Wizard atribuindu-l primului panou.

- SBPS_NOBORDER - arată că nu se va desena nici o margine tridimensională în jurul panoului.

- SBPS_POPOUT - indică trasarea unei margini inverse. - SBPS_NORMAL - creează o bară de stare fără extindere, margini sau efecte de

ieşire în relief. - SBPS_DISABLED - indică faptul că nu va scrie nici un text.

Un exemplu de bară de stare îl reprezintă adăugarea unui nou panou care să

indice ora curentă. - Adăugarea unui nou panou implică următoarele operaţii; - Adăugarea unui identificator în matricea de identificator; - Adăugarea unui articol de text prestabilit în tabelul de şiruri; - Adăugarea unui instrument de actualizare a comenzii pentru panoul respectiv.

44..44..33 AAddăăuuggaarreeaa uunnuuii iiddeennttiiffiiccaattoorr nnoouu

Exerciţiu 1. Pentru a defini un nou simbol de resursă, selectaţi caseta de dialog

Resource Symbols din meniul View. Apăsaţi butonul New şi introduceţi numele unui nou simbol ca fiind ID_INDICATOR_TIME.

În fişierul sursă MainFrame.cpp se află un vector UINT utilizat pentru a defini aspectul barei de stare. Modificaţi vectorul indicators astfel încât să arate ca în listingul următor:

static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, ID_INDICATOR_TIME }; Pentru a adăuga o resursă tabel de şiruri folosind simbolul

ID_INDICATOR_TIME se procedează astfel: - în eticheta Resource View din fereastra Project Workspace deschideţi resursa

String Table; - inseraţi un nou articol în tabela de şiruri cu valoarea ID_INDICATOR_TIME

drept identificator.

44..44..44 DDeeffiinniirreeaa ccrroonnoommeettrruulluuii şşii aa ssttiilluurriilloorr ddee ppaannoouurrii

Page 124: Programare Orientata Pe Obiecte

123

Proprietăţile unui panou din bara de stare se stabilesc apelând funcţia

SetPaneInfo . m_wndStatusBar.SetPaneInfo(4 , ID_INDICATOR_TIME , SBPS_POPOUT ,

80); Funcţia SetPaneInfo are patru parametrii: indexul panoului, indentificatorul

panoului, stilul panoului şi lăţimea acestuia. Stilurile de panou disponibile sunt: - SBPS_STRECH - permite extinderea panoului pentru a acoperi spaţiul nefolosit; - SBPS_NOBORDER - arată că nu se va desena nici o margine tridimensională în

jurul panoului; - SBPS_POPOUT - indică trasarea unei margini inverse; - SBPS_NOMAL - creează o bară de stare fără extindere, margini sau efecte de

ieşire în relief; - SBPS_DISABLED - indică faptul că nu va fi scris nici un text.

Definiţi un stil pentru noul articol din bara de stare şi iniţializaţi un cronometru

pentru fiecare secundă în funcţia CMainFrame::OnCreate . m_wndStatusBar.SetPaneInfo(4 , ID_INDICATOR_TIME ,

SBPS_POPOUT , 80); SetTimer(1,1000,NULL);

Tratarea resursei de ceas

Utilizând Class Wizard, adăugaţi o funcţie de tratare a mesajelor pentru

WM_TIMER în clasa CmainFrame. Această funcţie este apelată atunci când ceasul setat cu ajutorul funcţiei SetTimer expiră.

void CMainFrame::OnTimer(UINT nIDEvent) { m_wndStatusBar.InvalidateRect(NULL); } Atunci când timpul expiră, panoul principal va invalida dreptunghiul barei de

state, determinând redesenarea acesteia. Când bara de stare este invalidată, panoul MFC actualizează fiecare panou

folosind un instrument CCmdUI. Deşi se poate folosi ClassWizard pentru a crea asemenea instrumente pentru majoritatea obiectelor de interfaţă cu utilizatorul, instrumentele pentru manevrarea panourilor unei bare de stare trebuie create manual.

Adăugaţi o declaraţie pentru funcţia de actualizare CCmdUI în declaraţia clasei CMainFrame. Este necesară adăugarea unei singure linii de cod, şi anume declaraţia pentru OnUpdateTimer:

protected:

//{{AFX_MSG(CMainFrame)

Page 125: Programare Orientata Pe Obiecte

124

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnTimer(UINT nIDEvent); //}}AFX_MSG afx_msg void OnUpdateTimer(CCmdUI* pCmdUI); DECLARE_MESSAGE_MAP() În continuare, adăugaţi intrarea în harta de mesaje din MainFrame.cpp, după

cum urmează: BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_WM_TIMER() //}}AFX_MSG_MAP ON_UPDATE_COMMAND_UI(ID_INDICATOR_TIME,

OnUpdateTimer) END_MESSAGE_MAP() Adăugaţi funcţia OnUpdateTimer în fişierul MainFrame.cpp: void CMainFrame::OnUpdateTimer(CCmdUI* pCmdUI){

//Activarea panoului pCmdUI->Enable(); //Obtinerea orei curente CTime theTime=CTime::GetCurrentTime(); CString szTime=theTime.Format("%I:%M:%S %p");

//Completarea panoului cu ora curenta pCmdUI->SetText(szTime);

} Compilaţi şi executaţi exemplul creat. Acum, bara de stare are un nou panou,

situat la extremitatea dreaptă, care conţine ora curentă.

44..44..55 CCrreeaarreeaa uunneeii bbaarree ddee ddiiaalloogg -- CCDDiiaalloogg Barele de dialog sunt asemănătoare casetelor de dialog nemodale, cu deosebire

că pot fi fixate pe panoul principal sau pot fi flotante în spaţiul de lucru Windows. Această proprietate le face să semene cu barele de instrumente, dar pe deasupra mai pot conţine orice tip de control.

Spre deosebire de clasa CDialog , aici nu este nevoie de derivarea unei clase din CDialogBar. Mesajele primite de la controalele din bara de dialog sunt direcţionate către posesorul barei – CMainFrame. Deoarece Class Wizard nu gestionează aceste hărţi de mesaje, operaţiunea trebuie efectuată manual.

Crearea unei resurse casetă de dialog

Page 126: Programare Orientata Pe Obiecte

125

Exerciţiu 2. Utilizând editorul de casete de dialog Developer Studio, creaţi o nouă resursă casetă de dialog, folosind ca identificator IDD_BAR. Adăugaţi la caseta de dialog un buton cu identificatorul IDC_BAR_HELP.

Noul obiect CDialogbar este o variabilă membru a clasei CMainFrame. Pentru aceasta completaţi declaraţia clasei astfel:

CDialogBar m_dlgBar;

44..44..66 CCrreeaarreeaa uunneeii bbaarree ddee ddiiaalloogg aannccoorraabbiillee Pentru a pregăti o bară de dialog în vederea fixării, este necesar un apel la

funcţia CControlBar::EnableDocking(). Unicul parametru al funcţiei EnableDocking specifică modul de aliniere

al barei. Acesta poate avea una din valorile: - CBRS_ALIGN_TOP - permite fixarea pe marginea de sus a zonei client. - CBRS_ALIGN_BOTTOM - permite fixarea pe marginea de jos a zonei client. - CBRS_ALIGN_LEFT - permite fixarea pe marginea stângă a zonei client. - CBRS_ALIGN_RYGHT - permite fixarea pe marginea dreaptă a zonei client. - CBRS_ALIGN_ANY - permite fixarea pe orice margine a zonei client. - CBRS_SIZE_DYNAMIC - arată că bara este dinamică. - CBRS_FLOATING - arată că bara este flotantă. - CBRS_SIYE_FIXED - arată că bara este fixă. - CBRS_SIYE_INPLACE - arată că bara nu este afişată utilizatorului. - CBRS_FLOAT_MULTI - permite ancorarea mai multor bare de instrumente în

acelaşi rând al ferestrei panou. Când este creată o bară de dialog ancorabilă, titlul ferestrei trebuie setat folosind

funcţia SetWindowtext. Aceasta păstrează titlul barei de dialog atunci când bara este deplasată în afara ferestrei cadru.

Bara de dialog este creată şi iniţializată în funcţia CMainFrame::OnCreate. m_dlgBar.Create(this, IDD_BAR,CBRS_TOP,IDD_BAR); m_dlgBar.SetWindowText("Bara de dialog"); m_dlgBar.EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_dlgBar); Deoarece mesajele provenite de la controalele situate într-o bară de dialog sunt

direcţionate către părintele barei de dialog, iar hărţile de mesaje nu sunt gestionate de Class Wizard, trebuie completată harta de mesaje din MainFrame.h, prin adăugarea liniei:

afx_msg void OnBarHelp(); şi cea din MainFrame.cpp cu linia următoare: ON_BN_CLICKED(IDC_BAR_HELP, OnBarHelp)

Page 127: Programare Orientata Pe Obiecte

126

În clasa CMainFrame se adaugă o funcţie OnBarHelp de tratare a mesajului IDC_BAR_HELP.

void CMainFrame::OnBarHelp() { AfxMessageBox("Heeeelp!!!"); }

Compilaţi şi executaţi proiectul. Observaţi faptul că bara poate fi

deplasată pe suprafaţa de lucru sau alipită ferestrei cadru a aplicaţiei.

44..44..77 BBaarree ddee iinnssttrruummeennttee -- CCTToooollBBaarr Barele de instrumente conţin butoane care se folosesc pentru a transmite

comenzi unei aplicaţii. De obicei, aceste a sunt folosite pentru a asigura un acces rapid şi simplu la comenzi care mai pot fi apelate şi prin intermediul unor porţiuni de meniu.

Barele de instrumente sunt asemănătoare barelor de dialog, cu deosebirea că acestea conţin numai butoane. Ca şi în cazul barelor de dialog, acestea pot fi ancorate de un cadru sau pot fi flotante.

Utilizând editorul de resurse, creaţi o nouă resursă toolbar cu identificatorul IDR_TOOLBAR.

Adăugaţi în header-ul clasei CMainFrame o variabilă membru protected de tip pointer la clasa CToolBar:

protected: CToolBar * m_pColorToolbar;

iar în implementarea clasei CMainFrame completaţi constructorul astfel: CMainFrame::CMainFrame():m_pColorToolbar(0){}

Adăugaţi un element de meniu ColorToolbar la apelul căruia bara de instrument devine vizibilă/invizibilă. Trataţi mesajul generat de apăsarea butonului în clasa CMainFrame.

void CMainFrame::OnViewColorsbar() { if (0 == m_pColorToolbar) { m_pColorToolbar = new CToolBar; if (0 == m_pColorToolbar->Create(this)) { return; } if (0 == m_pColorToolbar-

>LoadToolBar(IDR_TOOLBAR1)) { return; } m_pColorToolbar-

>EnableDocking(CBRS_ALIGN_ANY); DockControlBar(m_pColorToolbar); }

Page 128: Programare Orientata Pe Obiecte

127

else // Daca bara este vizibila se ascunde if(m_pColorToolbar->IsWindowVisible() == TRUE) ShowControlBar(m_pColorToolbar, FALSE,

FALSE); else // Daca bara este invizibila se afiseaza ShowControlBar(m_pColorToolbar, TRUE,

FALSE); }

Pentru a distruge pointerul la clasa CToolBar trataţi mesajul WM_DESTROY

folosind Class Wizard void CMainFrame::OnDestroy() { CFrameWnd::OnDestroy(); if (0 != m_pColorToolbar) { delete m_pColorToolbar; m_pColorToolbar = 0; } }

Adăugaţi butoane barei de instrumente şi trataţi-le corespunzător.

44..44..88 DDiiaalloogguurrii ccoommuunnee –– CCCCoommmmuunnDDiiaalloogg

Din aceasta categorie fac parte CFileDialog, CFontDialog,

CColorDialog, CPageSetupDialog, CPrintDialog, CFindReplaceDialog,COleDialog. Operaţii ca atribuirea de denumiri fişierelor, selectarea culorilor şi fonturilor

sunt atât de uzuale, încât Microsoft a decis să integreze în Windows 98 casete de dialog pentru a le realiza. În consecinţă, aceste casete de dialog se numesc casete de dialog comune. Exemplul următor prezintă metodele de programare pentru casetele de dialog comune în contextul unei aplicaţii complete.

Cu ajutorul AppWizard-ului creaţi o aplicaţie SDI, Dialog. Adăugaţi un articol de meniu numit Dialogs cu patru sub-articole: Open File Dialog, Save File Dialog, Font Dialog şi Color Dialog. Toate casetele de dialog ale aplicaţiei sunt controlate în clasa afişare. Adăugaţi funcţii de tratare a mesajelor la selectarea unei comenzi din meniul Dialogs:

void CDialogView::OnDialogsOpenfiledialog() { // Crează o matrice de filtre pentru fişiere char filters[] = "Test Files (*.tst)|*.tst|All Files (*.*)|*.*|";

//Crează un obiect CFileDialog care reprezintă caseta de //dialog Open File CFileDialog fileDlg(TRUE, NULL, "*.tst",NULL, filters,

NULL);

Page 129: Programare Orientata Pe Obiecte

128

//Afişează caseta de dialog pentru utilizator int result = fileDlg.DoModal(); if (result == IDOK) { //Extrage fişierul cu denumirea selectată m_openFileName = fileDlg.GetFileName(); //Forţează refacerea ferestrei Invalidate(); } } void CDialogView::OnDialogsSavefiledialog() {

//Crează un obiect şir pentru înregistrarea denumirii //fişierului curent CString fileName; //Crează o matrice de şiruri pentru filtrele de fişiere char filters[] = "Test Files (*.tst)|*.tst|All Files (*.*)|*.*|";

//Stabileşte dacă trebuie folosită denumirea prestabilită sau //cea a fişierului selectat if (m_saveFileName == "NO FILE NAME SELECTED") fileName = "default.tst"; else fileName = m_saveFileName;

//Crează un obiect CFileDialog care reprezintă caseta de //dialog Save File CFileDialog fileDlg(FALSE, "*.tst", fileName, OFN_HIDEREADONLY |

OFN_OVERWRITEPROMPT,filters, NULL); //Afişează caseta de dialog pentru utilizator int result = fileDlg.DoModal(); if (result == IDOK) { //Extrage fişierul cu denumirea selectată m_saveFileName = fileDlg.GetFileName(); Invalidate(); } } void CDialogView::OnDialogsFontdialog() {

// Crează un obiect CFontDialog care reprezintă caseta de //dialog Font CFontDialog fontDialog(&m_logFont); int result = fontDialog.DoModal(); if (result == IDOK) { //Şterge vechiul font şi crează fontul selectat delete m_pFont; m_pFont = new CFont; m_pFont->CreateFontIndirect(&m_logFont);

Page 130: Programare Orientata Pe Obiecte

129

Invalidate(); } } void CDialogView::OnDialogsColordialog() { CColorDialog colorDialog(m_color); int result = colorDialog.DoModal(); if (result == IDOK) { //Extrage culoarea selectată m_color = colorDialog.GetColor(); Invalidate(); } }

Toate funcţiile care afişează casete de dialog comune apelează funcţia

Invalidate(), care impune programului refacerea ferestrei cu noile valori selectate de utilizator. Funcţia OnDraw() a clasei afişare răspunde de operaţiile de refacere. Completaţi funcţia cu următorul cod:

//Afişează denumirile fişierelor selectate pDC->TextOut(20, 20, m_openFileName); pDC->TextOut(20, 40, m_saveFileName); //Selectează fontul în contextul de dispozitiv CString fontName = m_logFont.lfFaceName; CFont* oldFont = (CFont*)pDC->SelectObject(m_pFont); //Afişează un text cu fontul selectat pDC->TextOut(20, 60, fontName); //Reface fontul iniţial în contextul de dispozitiv pDC->SelectObject(oldFont); //Crează un obiect CBrush din culoarea selectată CBrush brush(m_color); //Selectează noua pensulă în contextul de dispozitiv CBrush* oldBrush =(CBrush*)pDC->SelectObject(&brush); //Desenează un dreptunghi cu culoarea selectată pDC->Rectangle(20, 150, 120, 250); //Reface pensula iniţială în contextul de dispozitiv pDC->SelectObject(oldBrush); Completaţi constructorul şi destructorul clasei afişare:

CDialogView::CDialogView(){ m_openFileName = "NO FILE NAME SELECTED"; m_saveFileName = "NO FILE NAME SELECTED"; m_logFont.lfHeight = 48; m_logFont.lfWidth = 0; m_logFont.lfEscapement = 0; m_logFont.lfOrientation = 0;

Page 131: Programare Orientata Pe Obiecte

130

m_logFont.lfWeight = FW_NORMAL; m_logFont.lfItalic = 0; m_logFont.lfUnderline = 0; m_logFont.lfStrikeOut = 0; m_logFont.lfCharSet = 16; m_logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; m_logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; m_logFont.lfQuality = PROOF_QUALITY; m_logFont.lfPitchAndFamily = VARIABLE_PITCH |

FF_ROMAN; strcpy(m_logFont.lfFaceName, "Times New Roman"); m_pFont = new CFont; m_pFont->CreateFontIndirect(&m_logFont); m_color = RGB(255,0,0); } CDialogView::~CDialogView(){ delete m_pFont; }

Completaţi header-ul clasei afişare cu variabilele membre folosite: CFindReplaceDialog* m_pFindDialog; COLORREF m_color; LOGFONT m_logFont; CFont* m_pFont; CString m_saveFileName; CString m_openFileName;

44..44..99 FFooii ddee pprroopprriieettăăţţii -- CCPPrrooppeerrttyyPPaaggee Foile de proprietăţi sunt un tip deosebit de casetă de dialog, folosite

pentru organizarea mai multor opţiuni. O foaie de proprietăţi ordonează opţiunile în serii de pagini cu etichete, fiecare pagină (denumită pagină de proprietăţi) conţinând un set de atribute corelate. Orice pagină din foaia de proprietăţi este la rândul său o casetă de dialog asociată cu clasa MFC CPropertyPage. Foaia de proprietăţi este, la rândul ei, un obiect al clasei CPropertySheet.

Exerciţiu 3. Creaţi o aplicaţie SDI, Property. Adăugaţi două resurse casete de

dialog: una numită Text Options şi una Color Options. În pagina cu eticheta Style selectaţi Child în caseta Style, Thin în caseta Border şi dezactivaţi opţiunea System Menu. Acestea sunt stilurile de fereastră cerute de o pagină de proprietăţi. Configuraţi casetele de dialog astfel: 1. În caseta de dialog Text Options adăugaţi trei controale tip validare, creaţi o nouă

clasă pentru caseta de dialog, derivată din clasa de bază CPropertyPage şi trei variabile membre de tip BOOL . Procedaţi similar şi cu caseta Color Options

Page 132: Programare Orientata Pe Obiecte

131

adăugând trei controale de editare pentru valorile RGB şi asociaţi-le variabile membre întregi.

2. Creaţi o clasă foaie de proprietăţi, CMyPropertySheet derivată din clasa de bază CPropertySheet.

3. Acum, după definirea resurselor foii de proprietăţi şi după derivarea claselor paginilor şi foii de proprietăţi din CPropertyPage şi CPropertySheet, veţi scrie codul sursă pentru afişarea şi utilizarea foii de proprietăţi:

3.1.Includeţi fişierele antet ale paginilor de proprietăţi în clasa foii de proprietăţi:

#include "TextOptionsPage.h" #include "ColorOptionsPage.h"

3.2. Creaţi obiectele pagină de proprietăţi ca variabile membru ale clasei foaie de proprietăţi:

CColorOptionsPage m_colorPage; CTextOptionsPage m_textPage; 3.3. Pentru a vă permite să adăugaţi pagini de proprietăţi, clasa CPropertySheet

foloseşte funcţia membru AddPage(). Adăugaţi paginile de proprietăţi în foaia de proprietăţi (în constructor):

AddPage(&m_textPage); AddPage(&m_colorPage); 3.4. În continuare va trebui să includeţi header-ul clasei foii de proprietăţi în

fişierul sursă care afişează foaia de proprietăţi. Adesea, într-o aplicaţie generată cu AppWizard, aceasta este clasa afişare.

#include "MyPropertySheet.h" 3.5. Metoda de afişare a foii de proprietăţi nu diferă de cea a casetelor de dialog.

Se defineşte obiectul foaie de proprietăţi şi se apelează funcţia membru DoModal(): CMyPropertySheet propertySheet("My Property Sheet"); int result = propertySheet.DoModal(); Când creează foaia de proprietăţi, constructorul cere un argument, care este titlul

ce trebuie să apară pe bara de titlu a foii de proprietăţi. Transferul de valori dintre program şi foaia de proprietăţi funcţionează la fel ca în cazul casetelor de dialog. Pentru a introduce date în controalele foii de proprietăţi înaintea afişării ei, trebuie să iniţializaţi variabilele membru ale claselor paginilor de proprietăţi. Pentru aceasta adăugaţi clasei afişare trei variabile întregi şi trei de tip bool şi iniţializaţi-le corespunzător. În sens invers puteţi extrage informaţii din paginile de proprietăţi copiind valorile variabilelor membre ale paginilor de proprietăţi în variabilele definite anterior.

Page 133: Programare Orientata Pe Obiecte

132

Adăugaţi un articol de meniu Test cu sub-articolul PropertySheet şi funcţia de tratare a mesajului:

void CPropertyView::OnTestPropertysheet() { // TODO: Add your command handler code here CMyPropertySheet propertySheet("My Property Sheet"); propertySheet.m_textPage.m_check1 = m_check1; propertySheet.m_textPage.m_check2 = m_check2; propertySheet.m_textPage.m_check3 = m_check3; propertySheet.m_colorPage.m_edit1 = m_edit1; propertySheet.m_colorPage.m_edit2 = m_edit2; propertySheet.m_colorPage.m_edit3 = m_edit3; int result = propertySheet.DoModal();

if (result == IDOK) { m_check1 = propertySheet.m_textPage.m_check1; m_check2 = propertySheet.m_textPage.m_check2; m_check3 = propertySheet.m_textPage.m_check3; m_edit1 = propertySheet.m_colorPage.m_edit1; m_edit2 = propertySheet.m_colorPage.m_edit2; m_edit3 = propertySheet.m_colorPage.m_edit3;

Invalidate(); } }

Pentru ca modificările făcute în foaia de proprietăţi asupra valorilor controalelor

să fie afişate după închiderea foii, adăugaţi următorul cod funcţiei OnDraw(): pDC->TextOut(20, 20, "TEXT OPTIONS PAGE"); if (m_check1) pDC->TextOut(20, 40, "Check1 is checked"); else pDC->TextOut(20, 40, "Check1 is not checked"); if (m_check2) pDC->TextOut(20, 60, "Check2 is checked"); else pDC->TextOut(20, 60, "Check2 is not checked"); if (m_check3) pDC->TextOut(20, 80, "Check3 is checked"); else pDC->TextOut(20, 80, "Check3 is not checked"); pDC->TextOut(20, 120, "COLOR OPTIONS PAGE"); char str[81]; wsprintf(str, "Red = %d", m_edit1); pDC->TextOut(20, 140, str); wsprintf(str, "Green = %d", m_edit2); pDC->TextOut(20, 160, str); wsprintf(str, "Blue = %d", m_edit3); pDC->TextOut(20, 180, str);

Page 134: Programare Orientata Pe Obiecte

133

4.5 Lucrarea de laborator 5. Visual C++ MFC Utilizarea controalelor.

44..55..11 CCoonnttrroolluull ddee ttiipp bbuuttoonn –– CCBBuuttttoonn Acest control este folosit pentru realizarea unei acţiuni rapide fiind un element

de interfaţă accesibil în special cu mouse-ul dar şi prin tastatură. El are trei stări principale liber, selectat şi apăsat. Pentru a experimenta efectiv cu un buton vom crea un program prin MFC App Wizard – alegem ca tip de aplicaţie Dialog Based, la pasul 2 şi 3 apăsăm Next, apoi Finish. Va apărea fereastra de creare – modificare a interfeţei. În aceasta fereastră putând adăuga orice control disponibil. Selectăm un buton. Îl dispunem pe spaţiul afişabil al ferestrei. Pentru a modifica proprietăţile implicite vom face clic cu butonul 2 al mouse-ului pe el. Alegem ultima opţiune din meniu – Properties.

S-a deschis fereastra de mai sus. La caracteristici generale avem ID-ul butonului

care este un nume de identificare recunoscut pe tot parcursul programului şi disponibil pentru orice element de interfaţă. La Caption avem textul ce va fi scris pe buton. Apoi o serie de opţiuni ce se pot seta prin casete de validare. Principalul lucru care ne interesează este legare butonului de o acţiune anume. Pentru aceasta vom avea nevoie de ID-ul butonului.

Prin clic pe buton se va deschide o fereastră program în care avem corpul funcţiei în care vom scrie codul util ce va fi realizat la apăsarea butonului:

void CS07Dlg::OnButton1() { // Aici adaugati codul ce se executa la apasarea butonului } Unde S07 este numele programului. Şi funcţia OnButton() este acţiunea asociată

butonului 1. Dacă în schimb dorim să facem anumite modificări asupra butonului prin

program,stau la dispoziţie o serie de funcţii membri dintre care s-au selectat cele mai reprezentative:

GetIcon,SetIcon – pentru citirea,respectiv asocierea unui icon butonului;

Page 135: Programare Orientata Pe Obiecte

134

GetBitmap,SetBitmap – pentru citirea,respectiv asocierea unui bitmap pentru suprafaţa butonului;

GetCursor,SetCursor – pentru citirea, respectiv asocierea unui pointer de mouse diferit când aceste survolează butonul.

Pentru a accesa o funcţie membră a butonului se scrie următoarea secvenţa de

cod: CButton *mybutton=(CButton *)GetDlgItem(IDC_BUTTON1); mybutton->functie_membra; Prin aceasta avem un pointer la resursa de tip buton cu ID-ul IDC_BUTTON1.

Prin folosirea aceleaşi secvenţe cu specificarea clasei controlului, respectiv a ID-ului asociat acelui control, putem avea acces la oricare din membrii clasei controlului.

GetButtonStyle,SetButtonStyle – returnează, respectiv setează stilul butonului. GetWindowText,SetWindowText – returnează, respectiv setează textul

butonului. Exemplu: mybutton->SetWindowText("De acord!"); char s[100]; //pentru a extrage textul de pe buton mybutton->GetWindowText(s,100);//textul este returnat în variabila s Exerciţiu 1. Creaţi un program după modelul precizat mai sus. Creaţi un buton

care iniţial indică „Liber”, iar după apăsare „Apăsat!”. Numai cu funcţiile SetWindowText şi GetWindowText încercaţi să creaţi un program care să numere până la cinci la fiecare apăsare crescând cu o unitate numărul şi afişând numărul pe buton. O altă soluţie ar fi următoarea:

1. Declaraţi o variabilă contor k în fisierul header xxxdlg.h, în clasa xxxDlg la

secţiunea protected. 2. Iniţializaţi contorul k la 0 şi afişaţi valoare pe buton în xxxDlg::OnInitDialog:

CButton *mybutton=(CButton *)GetDlgItem(IDC_BUTTON1); k=0; char s[100]; sprintf(s,"%d",k); mybutton->SetWindowText(s);

3. In funcţia de acţiune a butonului veţi scrie următoarea secvenţă de cod:

void CS07Dlg::OnButton1() { CButton *mybutton=(CButton *)GetDlgItem(IDC_BUTTON1); k++; char s[100];

Page 136: Programare Orientata Pe Obiecte

135

sprintf(s,"%d",k); mybutton->SetWindowText(s); }

4. Compilaţi şi rulaţi.

44..55..22 CCoonnttrroolluull ddee ttiipp ccaasseettăă ddee eeddiittaarree –– CCEEddiitt CEdit este un tip de control ce permite introducerea unei secvenţe de text pe

un singur rând. Puteţi seta proprietăţile casetei de editare din fereastra de editare a controlului la fel cum s-a procedat cu CButton. Deşi deţine mai multe funcţii membre specifice cele mai utile sunt cele ce returnează textul conţinut – GetWindowText, respectiv modifică textul conţinut SetWindowText. Alte funcţii membre:

GetLimitText,SetLimitText – returnează numărul maxim de caractere ce se

pot introduce prin tastare,sau inserare, respectiv stabileşte acest număr. GetModify() – returnează 0 dacă textul din caseta de editare nu a fost

modificat, altfel valoare diferită de 0. SetModify() – resetează steguleţul de modificare al conţinutului casetei

trecându-l pe starea nemodificat. SetSel – setează poziţionarea selecţiei pe textul din caseta. GetSel – returnează poziţia selecţiei textului din caseta. Undo – aduce textul din caseta la starea precedentă. Clear – şterge selecţia curenta. Copy – copie selecţia curentă în clipboard. Cut – şterge selecţia curentă nu înainte de a o copia în clipboard. Paste – inserează text din clipboard la poziţia curentă. SetReadOnly – setează accesul la textul din casetă putând interzice

modificarea lui. ShowCaret,HideCaret – arată ,respectiv ascunde cursurul. Exerciţiu 2. Creaţi un program cu o casetă text şi un buton care la apăsarea

butonului textul din casetă să fie transferat ca text pe buton. Exerciţiu 3.Creaţi un program cu două casete text şi un buton. La apăsarea

butonului textul selectat din prima casetă este înserat pe poziţia curentă a cursorului în cea de a doua caseta.

Page 137: Programare Orientata Pe Obiecte

136

44..55..33 CCoonnttrroolluull ddee ttiipp ccaasseettăă ddee vvaalliiddaarree –– CCCChheecckkLLiissttBBooxx Este folosit pentru a bifa / debifa o opţiune într-o interfaţă. Poate fi configurat prin interfaţă vizuală ca şi CButton. Dintre funcţiile membre specifice amintim: GetWindowText, SetWindowText – care returnează sau setează textul casetei de

validare la fel ca în cazul casetei de editare. GetCheck, SetCheck – returnează, respectiv setează starea bifării. GetCheckStyle, SetCheckStyle – returnează, respectiv setează stilul casetei. CCheckListBox *cb=(CCheckListBox *)GetDlgItem(IDC_CHECK1); cb->SetWindowText("Acum"); cb->SetCheckStyle(BS_CHECKBOX); cb->SetCheck(IDC_CHECK1,1); Este un exemplu de folosirea a acestor metode (funcţii membre). În privinţa stilurilor exista următoarele constante predefinite:

• BS_CHECKBOX – stilul precizează o casetă standard;

• BS_AUTOCHECKBOX - caseta se bifează sau se debifează doar prin simpla selectarea a acesteia;

• BS_AUTO3STATE – precizează o casetă cu trei stări care se bifează sau debifează prin selectare;

• BS_3STATE – precizează o casetă cu trei stări, astfel încât caseta poate fi în acelaşi timp şi bifată şi dezactivată.

Enable – setează starea de activare a casetei. IsEnabled – returnează starea de activare a casetei. Exerciţiu 4. Creaţi un program cu patru casete de validare şi o casetă de editare.

Casetele vor avea ca etichetă numerele 1,2,3,4. De fiecare dată când o casetă va fi bifată sau debifată în caseta de editare vor apărea etichetele casetelor bifate legate între ele prin „&”.

De exemplu dacă sunt bifate caseta 2 şi 4 în caseta de editare va apărea „2&4”.

Page 138: Programare Orientata Pe Obiecte

137

44..55..44 CCoonnttrroolluull ttiipp bbuuttoonn rraaddiioo Butoanele radio permit selectarea doar unei opţiuni dintr-un grup de opţiuni. Aceste butoane pot fi grupate astfel încât să avem diverse grupuri de opţiuni. Se

observă ca la selectarea unei opţiuni cea anterior selectată se deselectează. Pentru a accesa textul unui buton radio vom folosi următoarea secvenţă:

GetDlgItem(IDC_RADIO1)->SetWindowText("Asteptare!"); De notat că butoanele radio nu au o clasă specific definită putând fi încărcate cu

CCheckListBox, chiar şi cu CButton, desigur metodele nu vor fi întotdeauna valabile. Exerciţiu 5. Creaţi un program cu patru butoane radio grupate având etichetele

nume de persoane şi o casetă de editare. În momentul când butonul radio va fi selectat în casetă va apărea numele persoanei alese.

44..55..55 CCoonnttrroolluull ddee ttiipp lliissttaa ccoommbboo –– CCCCoommbbooBBooxx

Permite selectarea unui şir de caractere dintr-o listă retractabilă. În momentul în

care este selectată se desfăşoare lista şi poate fi selectată opţiunea dorită. Cele mai utile metode ale CComboBox sunt cele de construire a listei:

AddString(s) – adaugă un şir de caractere la sfârşitul listei sau la poziţia indexată

alfabetic dacă opţiunea de auto-sortare a listei este activată; DeleteString(index) – şterge un element din listă de pe poziţia specificată de

index; InsertString(index,s) –inserează un şir s în listă la poziţia specificată de index; FindString – caută un sub-şir în listă şi dacă îl găseşte îi returnează indexul; Exemplu: CComboBox *lista=(CComboBox *)GetDlgItem(IDC_COMBO1); lista->AddString("Andrei"); lista->AddString("Adrian"); lista->AddString("Radu"); lista->AddString("Mihai"); Metode pentru lucrul cu indexul: GetCount – returnează numărul de elemente din listă; GetCurSel – returnează poziţia elementului selectat din listă; SetCurSel – setează elementul curent selectat copiindu-l în caseta de editare, de

notat că primul element are indicele 0; Clear,Copy,Cut, Paste – şterge, copie, decupează,respectiv adaugă text din

caseta de editare în sau din Clipboard; GetLBText – obţine textul unui element din listă de la poziţia specificată de un

index;

Page 139: Programare Orientata Pe Obiecte

138

Exerciţiu 6. Realizaţi un program cu o listă de ţări europene cel puţin zece.

Creaţi şi o casetă de editare. În momentul în care aţi selectat o ţară numele acesteia să fie copiat în caseta de editare.

44..55..66 CCoonnttrroolluull ddee ttiipp tteexxtt ssttaattiicc:: CCSSttaattiicc CStatic este un control care doar afişează un text la o anumită locaţie. Acest text

poate fi modificat ca şi la celelalte controale prezentate cu ajutorul funcţiei SetWindowText şi poate fi aflat acel text cu GetWindowText. Un exemplu ar fi următoarea structură:

CStatic *st=(CStatic *)GetDlgItem(IDC_STATIC); st->SetWindowText("Aceasta este un text static."); Exerciţiu 7. Modificaţi programul precedent astfel încât în dreptul casetei de

editare să apară „tara aleasa”, iar în dreptul listei „tari disponibile”.

44..55..77 MMeettooddaa CCrreeaattee Metoda Create poate fi folosită pentru generarea de butoane din program fără

folosirea interfeţei vizuale. Pentru a genera un buton se poate folosi codul: CRect r; r.left=10; r.right=10+200; r.top=10; r.bottom=10+20; myb.Create("Buton din program", WS_CHILD | WS_VISIBLE

| BS_PUSHBUTTON,r,this,1112); Pentru a declara variabila globală myb – se procedează astfel: se setează

opţiunea tabulată de vizualizare a clasei. Se dă clic dreapta pe CxxxDlg se alege opţiunea Add Member Variable.

Exerciţiu 8. Declaraţi o matrice de 10 pe 10 de butoane.(CButton … ). În

OnInitDialog al clasei CxxxDlg creaţi aceste butoane. Butoanele vor fi dispuse pe 10 linii şi pe 10 coloane, iar pe butoane vor fi scrise numerele de la 1 la 100.

Page 140: Programare Orientata Pe Obiecte

139

4.6 Lucrarea de laborator 6. Visual C++ MFC

44..66..11 UUttiilliizzaarreeaa ffuunnccţţiiiilloorr ggrraaffiiccee.. Pentru a lucra cu funcţiile grafice oferite în cadrul MFC vom aborda următoarea

opţiune de generare a programului schelet : 1. Selectăm MFC App Wizard (.exe) şi scriem numele proiectului; 2. Alegem Single Document (SDI) şi apăsăm Next. 3. La pasul 2 apăsăm Next. 4. La pasul 3 apăsăm Next. 5. Debifăm Docking Toolbar, Initial Status Bar, Printing and print preview, apăsăm

Finish. Va apărea în stânga ecranului clasele disponibile. Alegem CxxxView expandăm

ramura şi apăsam pe OnDraw. Cursurul se va poziţiona automat în fereastra codului sursă:

void CSs01View::OnDraw(CDC* pDC) { CSs01Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // aici se inserează codul care desenează }

44..66..22 DDeesseennaarreeaa uunneeii lliinniiii Nu există efectiv o funcţie care să traseze o linie, în schimb acest lucru se poate

realiza prin conlucrarea a două funcţii MoveTo care stabileşte poziţia unui capăt al liniei şi LineTo care stabileşte poziţia celuilalt capăt şi trasează linia. Ele au următoarele prototipuri:

CPoint MoveTo( int x, int y ); CPoint MoveTo( POINT point ); BOOL LineTo( int x, int y ); BOOL LineTo( POINT point ); Astfel se pot apela fie specificând coordonatele x, y ale capetelor, fie specificând

punctul capătului. În program vom scrie: pDC->MoveTo(10,10); pDC->LineTo(100,100); ,pentru a desena o linie cu setările implicite ale culorii, grosimii şi stilului de

desenare.

Page 141: Programare Orientata Pe Obiecte

140

Desenarea unui arc de cerc se poate face cu trei funcţii distincte :

1. Arc cu prototipurile : BOOL Arc( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd );

2. ArcTo cu prototipurile:

BOOL ArcTo( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL ArcTo( LPCRECT lpRect, POINT ptStart, POINT ptEnd );

3. AngleArc cu prototipul: BOOL AngleArc( int x, int y, int nRadius, float fStartAngle, float fSweepAngle

); Rezultatul este acelaşi trasarea unui arc de cerc. Vom face referire la doar un

singur prototip cel expandat. Pentru Arc :

– x1, y1 coordonatele colţului stânga sus al zonei rectangulare de încadrarea ; – x2, y2 coordonatele colţului dreapta jos al zonei rectangulare de încadrarea ; – x3, y3 punctul de început al arcului ; – x4, y4 punctul de sfârşit al arcului de cerc ;

Pentru ArcTo aceleaşi precizări numai că poziţia curentă a cursorului este reînnoită.

Pentr AngleArc : - x, y coordonatele centrului cercului ; - Radius este raza cercului ; - StartAngle reprezintă unghiul de start al desenării arcului ; - SweepAngle reprezintă mărimea unghiulară a arcului.

Exemplu: pDC->Arc(10,10,200,200,10,10,200,200); pDC->ArcTo(10,10,200,200,10,10,200,200); pDC->AngleArc(100,100,100,10,90);

44..66..33 DDeesseennaarreeaa uunneeii lliinniiii ppoolliiggoonnaallee Pentru a desena o linie poligonala folosim funcţiile : PolyDraw, Polyline,

PolyPolyline, PolylineTo, PolyBezier, PolyBezierTo care au următoarele prototipuri: BOOL PolyDraw( const POINT* lpPoints, const BYTE* lpTypes, int nCount ); BOOL Polyline( LPPOINT lpPoints, int nCount ); BOOL PolyPolyline( const POINT* lpPoints, const DWORD* lpPolyPoints, int

nCount );

Page 142: Programare Orientata Pe Obiecte

141

BOOL PolylineTo( const POINT* lpPoints, int nCount ); BOOL PolyBezier( const POINT* lpPoints, int nCount ); BOOL PolyBezierTo( const POINT* lpPoints, int nCount ); PolyDraw desenează o linie poligonală cu un anumit tipic :

- lpPoints este un pointer la un vector de puncte. - lpTypes este un pointer la un vector care specifică pentru fiecare punct specificul

desenării ; - nCount este numărul de puncte desenate din vector.

Polyline desenează o linie poligonală deschisă fără a modifica poziţia cursorului .

PolyPolyline desenează o serie de linii poligonale acestea fiind specificate una după alta în lpPoints, iar numărul de puncte al fiecărei linii este specificat în lpPolyPoints.

PolylineTo desenează o linie poligonală reînnoind poziţia cursorului. PolyBezier desenează o curbă Bezier ale cărei puncte generative sunt specificate

în lpPoints. PolyBezier desenează o curbă Bezier cu reînnoire cursorului. Exemplu : CPoint p[100]; int i; for (i=1;i<=50;i++) { p[i-1].x=i*5; p[i-1].y=100-(i%2)*40; } pDC->Polyline(p,50); Această secvenţă va desena o linie în zigzag. Mai sunt funcţiile de specificare a direcţiei de desenare : GetArcDirection.,

SetArcDirection cu următoarele prototipuri: int GetArcDirection( ) const; int SetArcDirection( int nArcDirection ); Unde nArcDirection poate lua unu din următoarele valori constante : AD_COUNTERCLOCKWISE - specifică că sensul va fi cel invers acelor de

ceas; AD_CLOCKWISE - specifică că sensul va fi cel al acelor de ceas. Acerte funcţii influenţează sensul în care se trasează următoarele primitive

grafice: Arc, ArcTo, Chord, Ellipse, Pie, Rectangle, RoundRect.

Page 143: Programare Orientata Pe Obiecte

142

44..66..44 FFuunnccţţiiii ddee ddeesseennaarree ddee ffiigguurrii uummpplluuttee:: Pentru desenarea unei regiuni eliptice mărginite de o dreaptă folosim Chord cu

următoarele prototipuri : BOOL Chord( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); Semnificaţiile perechilor x,y sunt acelaşi ca la Arc. Pentru desenarea unui dreptunghi se foloseşte Rectangle cu următoarele

prototipuri : BOOL Rectangle( int x1, int y1, int x2, int y2 ); BOOL Rectangle( LPCRECT lpRect ); Unde x1,y1, x2,y2 sunt coordonatele colţului stânga-sus, respectiv dreapta-jos

ale dreptunghiului. În cel de-al doilea prototip pentru a specifica aceste coordonate se foloseşte o structură de tipul CRect.

Pentru desenarea unui dreptunghi având colţuri rotunjite se foloseşte RoundRect

cu prototipurile : BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 ); BOOL RoundRect( LPCRECT lpRect, POINT point ); Unde primele patru valori, pentru primul prototip, sunt acelaşi ca pentru

Rectangle, iar ultimele două valori nu sunt coordonate ci raza pe ox, respectiv pe oy ale elipsei folosite la rotunjirea colţurilor.

Pentru desenarea unei elipse şi eventual cerc, se foloseşte Ellipse cu următoarele

prototipuri: BOOL Ellipse( int x1, int y1, int x2, int y2 ); BOOL Ellipse( LPCRECT lpRect ); Unde x1, y1, x2, y2 specifică dreptunghiul ce încadrează elipsa, la fel fiind şi

cazul celui de al doilea prototip. Pentru desenarea unei regiuni eliptice delimitate de două raze se foloseşte

funcţia Pie cu următoarele prototipuri:

Page 144: Programare Orientata Pe Obiecte

143

BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); În care primele patru coordinate sunt aceleaşi ca la elipsă, următoarele patru

specificând punctual de început şi de sfârşit al regiunii. Pentru a desena un polygon închis se foloseşte Polygon care are prototipul : BOOL Polygon( LPPOINT lpPoints, int nCount ); Se observă ca avem aceeaşi parametri ca la Polyline, deosebire fiind că punctul

de început este unit automat cu punctul de sfârşit închizând astfel conturul poligonal. O variantă a acestei funcţii este PolyPolygon cu prototipul: BOOL PolyPolygon( LPPOINT lpPoints, LPINT lpPolyCounts, int nCount ); Care se comportă la fel ca PolyPolyline în ceea ce priveşte funcţia parametrilor

utilizaţi.

44..66..55 SSeettaarreeaa ccuulloorriiii şşii aa ssttiilluulluuii ddee ddeesseennaarree Până acum s-a precizat cum se desenează o serie de primitive dar aceste sunt

toate în alb şi negru desenate cu umplere sau trasare simplă fără a utiliza un stil anume. Pentru a modifica peniţa cu care se trasează liniile se va crea o peniţă proprie

prin folosirea clasei CPen. Această peniţă ne permite să schimbăm culoarea, grosimea liniei, şi stilul liniei (linie întreruptă). Pentru a modifica culoare de umplere vom crea o pensulă proprie ce ne va permite să stabilim culoare de umplere, modelul de umplere, stilul de umplere. Pensula va fi definită prin utilizarea clasei CBrush.

44..66..66 CCoonnffiigguurraarreeaa ppeenniiţţeeii

Pentru a crea propria peniţă se declara un obiect de tip CPen. Se apelează funcţia

Create cu următoarele prototipuri : BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor ); BOOL CreatePen( int nPenStyle, int nWidth, const LOGBRUSH* pLogBrush,

int nStyleCount = 0, const DWORD* lpStyle = NULL ); Unde :

- nPenStyle – specifică stilul peniţei; - nWidth – grosimea peniţei; - crColor – reprezintă culoare peniţei specificate în format RGB;

Stilul peniţei poate fi precizat prin următoarele constante:

- PS_COSMETIC – specifică că modelul peniţei este specificat în lpStyle;

Page 145: Programare Orientata Pe Obiecte

144

- PS_GEOMETRIC – specifică că modelul va fi cu linii întrerupte a căror lungimi sunt specificate în lpStzle;

- PS_ALTERNATE – alternează un punct lipsă unul desenat în model; - PS_USERSTYLE – specifică un model specific definit de utilizator în lpStyle.

Exemplu de utilizare al peniţei: CPen mpen; mpen.CreatePen(PS_GEOMETRIC,3,RGB(0,0xFF,0)); CPen *oldpen=pDC->SelectObject(&mpen); CPoint p[100]; int i; for (i=1;i<=50;i++) { p[i-1].x=i*5; p[i-1].y=100-(i%2)*40; } pDC->Polyline(p,50); pDC->SelectObject(oldpen); Se va desena un zigzag verde de grosime 3. Pentru a nu mai apărea marginea unei figuri desenate ci doar umplere se setează

grosimea peniţei la 0.

44..66..77 CCoonnffiigguurraarreeaa ppeennssuulleeii Pentru a configura pensula se foloseşte clasa CBrush. Pentru a crea o pensulă se

dispune de un set bogat de metode : - CreateSolidBrush – creează o pensulă cu umplere uniformă ; - CreateHatchBrush – creează o pensulă cu umplere haşurată; - CreateBrushIndirect – creează o pensulă prin intermediul unei structuri

LOGBRUSH; - CreatePatternBrush – creează o pensulă la care modelul de umplere este un

bitmap; - CreateDIBPattternBrush – creează o pensulă la care modelul de umplere este un

bitmap independent de context; - CreateSysColorBrush – creează o pensulă având culoare specificată de sistem,

modificând doar stilul de haşurare. CreateSolidBrush are următorul prototip: BOOL CreateSolidBrush( COLORREF crColor ); În care doar se specifică culoare de umplere, stilul de umplere va fi unul

uniform.

Page 146: Programare Orientata Pe Obiecte

145

Exemplu : CPen mpen; mpen.CreatePen(PS_GEOMETRIC,3,RGB(0,0xFF,0)); CPen *oldpen=pDC->SelectObject(&mpen); CBrush mbrush; mbrush.CreateSolidBrush(RGB(0,0xFF,0xFF)); CBrush *oldbrush=pDC->SelectObject(&mbrush); CPoint p[100]; pDC->Ellipse(10,10,100,100); pDC->SelectObject(oldpen); pDC->SelectObject(oldbrush); CreateBrushIndirect are următorul prototip: BOOL CreateBrushIndirect( const LOGBRUSH* lpLogBrush ); Este cel mai indicat a fi folosit când se doreşte un configurare completa a

pensulei. Face apel la structura : LOGBRUSH typedef struct tagLOGBRUSH { // lb UINT lbStyle; COLORREF lbColor; LONG lbHatch; } LOGBRUSH; Unde lbStyle poate avea valorile:

- BS_DIBPATTERN – pentru un model de umplere definit în DIB în acest caz lbHatch va fi indica adresa logică a DIB-ului;

- BS_HATCHED – pentru un model cu haşură; - BS_HOLLOW – figura nu va fi umplută; - BS_NULL – figura nu va fi umplută ; - BS_PATTERN – modelul este specificat într-un bitmap; - BS_SOLID – modelul de umplere este cel uniform.

Pentru lbHatch avem următoarele opţiuni : - HS_BDIAGONAL - haşură la 45 de grade de la dreapta la stânga ; - HS_CROSS - haşură în carouri formate din linii verticale şi orizontale egal

spaţiate; - HS_DIAGCROSS - haşură la 45 de grade; - HS_FDIAGONAL - haşură la 45 de grade; - HS_HORIZONTAL - haşură prin linii orizontale; - HS_VERTICAL - haşură prin linii verticale;

Page 147: Programare Orientata Pe Obiecte

146

Exerciţiu 1. Să se deseneze două linii verticale paralele, tăiate de două linii orizontale paralele. Liniile vor fi întrerupte, de grosime 2 şi de culoare roşie.

Exerciţiu 2. Să se deseneze două arce de cerc care să fie sub forma a două

jumătăţi de cerc tangente. Exerciţiu 3. Să se deseneze o linie în zigzag la care lăţimea zigzagului să crească

liniar progresiv. Exerciţiu 4. Să se deseneze un dreptunghi albastru uniform iar în interiorul lui o

elipsă roşie haşurată la 45 de grade. Exerciţiu 5. Să se deseneze o stea în cinci colţuri de culoare galbenă. (Culoarea

galbenă este dată de funcţia RGB(0xFF,0xFF,0)).( Pentru a genera punctele poligonului mai uşor folosiţi funcţiile trigonometrice sin şi cos şi o alternare în zigzag a distanţei acestora faţă de centrul stelei.)

Page 148: Programare Orientata Pe Obiecte

147

4.7 Lucrarea de laborator 7. Visual C++ MFC

44..77..11 UUttiilliizzaarreeaa iinnttrrăărriilloorr ddee llaa mmoouussee şşii ttaassttaattuurrăă Oricare acţiune a utilizatorului prin mouse sau tastatură sau pornind de la mouse

şi tastatură, sau acţiune automată ciclică, care acţionează asupra ferestrei are corespondent într-o structură de gestionare a acestor acţiuni un mesaj sau eveniment. Lucrul cu mesaje este o caracteristică a aplicaţiilor Windows, aceasta a apărut cu primele variante de Windows 3.1, apoi a devenit un standard în Win32, OWL, respectiv MFC. Astfel progresiv s-a trecut de la o asociere manuală acestor mesaje unor funcţii acţiuni la o asociere semiautomată, respectiv automată. În ceea ce priveşte acţiunile mouse-lui orice acţiune unitară care nu mai poate fi redusă la alte acţiuni are asociat un mesaj sau un eveniment. Astfel unei apăsări a mouse-ului ,doar apăsare fără eliberare îi este asociată un eveniment şi dacă acesta are o utilitate pentru program i se poate asocia o funcţie în care să se gestioneze această acţiune. La fel şi pentru tastatură oricărei apăsări de tastă îi este asociat un eveniment.

Pentru a programa un eveniment, adică pentru a-i asocia o funcţie utilizăm următoarea abordare:

După cum se vede se apasă butonul din dreptul comboului în care este selectat

CSs02Dlg. Se apasă Add Windows Message Handler – cel încercuit cu roşu pentru a adăuga o funcţie care să gestioneze mesajul.

Page 149: Programare Orientata Pe Obiecte

148

Se deschide următoarea fereastră: În partea stângă se află lista mesajelor disponibile, în partea dreaptă listă

mesajelor cărora li s-au asociat funcţii. Se observă că denumirea mesajului are următorul tipic WM_nume_mesaj. Pentru a asocia o funcţie mesajului dorit se selectează mesajul din lista din partea stângă şi se apasă pe butonul Add Handler. Se poate adăuga astfel unul sau mai multe mesaje pentru a fi tratate de program.(sau niciunul prin apăsare butonului Cancel). Se observă că aici se pot adăuga mesaje pentru toate obiectele definite prin selectarea corespunzătoare a obiectului (lista Class or object to handle). De asemenea se dispune şi de un filtru. Cu toate că putem adăuga mai multe mesaje doar unuia i se va asocia o funcţie în cadrul codului sursă. Pentru a specifica cărui mesaj i se va asocia o funcţie se va face dublu clic pe acel mesaj mediul de programare va deschide automat fereastra codului sursă exact la funcţia asociată mesajului. Celelalte mesaje cărora nu li s-au asociat funcţii vor dispărea automat din lista mesajelor tratate de program. Având acces la funcţie putem programa acel eveniment. Observăm că în funcţie de evenimentul tratat funcţiei îi sunt asociate anumiţi parametri prin care se transmit informaţii de la eveniment.

Pentru gestionarea mouse-lui avem definite următoarele mesaje:

Page 150: Programare Orientata Pe Obiecte

149

- WM_LBUTTONDBLCLK – trimis dacă s-a făcut dublu-clic cu primul buton al mouselui;

- WM_LBUTTONDOWN – trimis dacă s-a apăsat primul buton al mouseului; - WM_LBUTTONUP – trimis dacă s-a eliberat al doilea buton al mouselui; - WM_MBUTTONDBLCLK – trimis dacă s-a făcut dublu clic pe butonul din

mijloc al mouseului; - WM_MBUTTONDOWN – trimis dacă s-a apăsat butonul mijlociu al mouselui; - WM_MBUTTONUP – trimis dacă s-a eliberat butonul mijlociu al mouseului; - WM_MOUSEMOVE – trimis dacă s-a deplasat mouseul; - WM_MOUSEWHEEL – trimis dacă s-a rotit rotiţa de derulare a mouseuui; - WM_RBUTTONDBLCLK – trimis dacă s-a făcut dublu clic pe butonul doi al

mouselui (butonul din dreapta în mod standard); - WM_RBUTTONDOWN – trimis dacă s-a apăsat butonul al doilea al mouseului; - WM_RBUTTONUP – trimis dacă s-a eliberat butonul al doilea al mouseului;

Mai sunt şi alte mesaje dar cu o folosire mai redusă. Se va observa că toate

funcţiile ce ţin de apăsarea unui buton al mouseului sunt funcţii identice ca parametri asociaţi.

Astfel pentru mesajul WM_XBUTTONDBLCLK vom avea următorul prototip al funcţiei :

afx_msg void OnXButtonDblClk( UINT nFlags, CPoint point ); , unde nFlags sunt o colecţie de indicatori, a căror stare poate fi analizată prin

constantele :

• MK_CONTROL - Setat dacă tasta CTRL este apăsată;

• MK_LBUTTON - Setat dacă primul buton al mouseului este apăsat;

• MK_MBUTTON - Setat dacă butonul din mijloc al mouseului este apăsat;

• MK_RBUTTON - Setat dacă butonul al doilea al mouseului este apăsat;

• MK_SHIFT - Setat dacă tasta SHIFT este ţinută apăsat;

point reprezintă poziţia curentă a mouselui – coordonata x este dată de point.x, iar y de point.y.

Pentru mesajul WM_XBUTTONDOWN avem următoarea funcţie asociată : afx_msg void OnXButtonDown( UINT nFlags, CPoint point ); Exemplu: void CSs02Dlg::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default

Page 151: Programare Orientata Pe Obiecte

150

CDialog::OnLButtonDown(nFlags, point); if (nFlags & MK_CONTROL){ CClientDC dc(this); dc.Rectangle(point.x-5,point.y-5,point.x+5,point.y+5); } } Această secvenţă de cod desenează un dreptunghi la poziţia curentă a mouse-ului

de fiecare dată când se apasă butonul stâng al mouselui, ţinându-se în acelaşi timp şi tasta CTRL apăsată.

Pentru WM_XBUTTONUP vom avea o funcţie asociată cu un prototip asemănător:

afx_msg void OnXButtonUp( UINT nFlags, CPoint point );

WM_MOUSEMOVE are o funcţie asociată cu următorul prototip: afx_msg void OnMouseMove( UINT nFlags, CPoint point ); WM_MOUSEWHEEL are o funcţie asociată cu următorul prototip: afx_msg BOOL OnMouseWheel( UINT nFlags, short zDelta, CPoint pt ); ,unde zDelta – specifică unghiul de rotaţie al rotiţei mouselui – poate avea valori

positive sau negative în funcţie de direcţia de rotaţie; şi pt reprezintă poziţia actuală a pointerului de mouse. Exerciţiu 1. Creaţi un program care la clic pe primul buton al mouselui să

deseneze la poziţia cursorului un dreptunghi, la clic pe al doilea buton o elipsă, la clic pe butonul din mijloc un x din linii centrat pe poziţia cursorului.

Exerciţiu 2. Creaţi un program care să deseneze linii cu mouse-ul la primul clic

se stabileşte primul capăt al liniei la al doilea celălalt capăt. Linia se trasează la al doilea clic.

Exerciţiu 3. Creaţi un program pentru desenarea de dreptunghiuri (rectangle) la

primul clic se stabileşte coordonatele primului colţ la cel de-al doilea coordonatele celui de al doilea colţ. La cel de-al doilea clic se trasează dreptunghiul.

44..77..22 GGeessttiioonnaarreeaa iinnttrrăărriilloorr ddee llaa ttaassttaattuurrăă

Intrările de la tastatură sunt gestionate prin mesaje care gestionează separat

intrările de taste alfa – numerice, respectiv simboluri (%,#,&,/…ş.a.m.d.) şi intrările de taste funcţionale ( tastele săgeţi, tab, F1…F12, Esc, PageUp, Page Down, Insert, Delete etc.).

Page 152: Programare Orientata Pe Obiecte

151

Pentru a utiliza aceste mesaje creaţi o aplicaţie gen SDI sau MDI, deoarece Dialog Based nu preia corect aceste mesaje.

Aceste mesaje sunt:

- WM_CHAR – tratează primul tip de intrări de tastatură fiind generat la apăsarea unei taste de acest tip;

- WM_KEYDOWN – generat la apăsarea şi ţinerea apăsată a unei taste funcţionale;

- WM_KEYUP – generat la eliberarea unei taste funcţionale;

Ele au asociate funcţiile cu următoarele prototipuri : afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags ); La OnChar – nChar specifică codul ASCII asociat caracterului generat de tasta

respectivă ; La OnKeyDown,OnKeyUp – nChar specifică codul virtual al tastei funcţionale ; nRepCnt – numărul de repetări ale tastei ca urmare a ţinerii apăsate a unei taste; nFlags –sunt indicatori ale tipului de tasta (pentru OnKeyDown, OnKeyUp) :

- biţii de la 0-7 specifică codul tastei specificat prin standardul OEM ; - bitul 8 – dacă este setat specifică o tastă extinsă, în cazul tastelor funcţionale ; - bitul 13 – indică dacă tasta ALT este apăsată ; - bitul 14 – indică o stare anterioară a tastei – 1 dacă tasta era apăsată înaintea

apelării şi 0 dacă tastă era liberă ; - bitul 15 – indică o stare de tranziţie -1 dacă tasta este eliberată, 0 dacă este

apăsată ; Pentru OnChar nFlags are următoarea semnificaţie :

- biţii 0-15 - specifică numărul de repetiţii ale tastei prin ţinerea ei apăsată; - biţii 16-23 - specifică codul de scanare al tastei definit de OEM ; - bitul 24 – specifică o tastă extinsă ; - ultimii trei biţi (29,30,31) au aceeaşi semnificaţie ca în cazul anterior ;

Page 153: Programare Orientata Pe Obiecte

152

44..77..33 FFoolloossiirreeaa aacccceelleerraattoorriilloorr

O altă cale de a avea acces la tastatură este prin folosirea acceleratorilor. Pentru a adăuga o resursă de tip accelerator vom accesa următorul meniu :

Page 154: Programare Orientata Pe Obiecte

153

Deci din modul de vizualizare resursă vom face clic dreapta pe nodul rădăcină şi

va apare un meniu, din aceste meniu vom alege Insert… pe care vom face dublu-clic. Va apare următoarea fereastră :

Vom alege Accelerator şi vom apăsa butonul New. Va apărea fereastra :

Page 155: Programare Orientata Pe Obiecte

154

Vom face dublu clic pe linie necompletată selectată. Apărând un meniu în care

efectiv vom specifica tasta, sau combinaţia de taste: Odată ce am definit această tastă pe bază de ID vom defini o funcţie asociată

prin utilizarea meniului de adăugare de mesaje.

Page 156: Programare Orientata Pe Obiecte

155

5 ÎNDRUMAR DE PROIECT

5.1 Laborator de proiect 1.

55..11..11 IInnttrroodduucceerree îînn pprrooiieeccttaarreeaa uunneeii aapplliiccaaţţiiii În cadrul acestui laborator se va urmări crearea unei aplicaţii software complete

folosind mediul vizual Visual C++. Pentru a face proiectul mai atractiv vom propune realizare de jocuri cu un cod relativ simplu. Acestea vor fi de patru tipuri:

- Joc de tenis, de spart cărămizi gen Arkanoid; - Joc de tetris, de realizat linii complete din blocuri de diferite forme; - Joc gen Dyna, de explorare a unui labirint şi distrugerea acestuia prin

bombe; - Joc gen Arcade Shooter, de distrugerea unor nave sau meteoriţi de către o

navă cu mişcare pe orizontală. Studenţii se pot grupa în echipe, astfel încât să-şi coordoneze reciproc acţiunile,

să-şi stabilească sarcinile, să-şi dezvolte astfel aptitudini de lucru în echipă. La final vor prezenta o lucrare colectivă. Eventual se va prezenta aportul fiecăruia la realizarea aplicaţiei – parte grafică, parte de codare, parte de algoritmi, parte de efecte sonore, grafice ale jocului, partea de scenariu etc.

În această etapă se vor stabili următoarele puncte: - comportarea minimală a jocului – adică cum trebuie să se desfăşoare un joc,

ce opţiuni de configurare şi de stil de joc să fie disponibile; - direcţii de dezvoltare ale jocului – adică la care anume element din joc să i se

crească complexitatea sau nivelul de interacţiune cu utilizatorul; - căutarea şi analizarea unor jocuri deja făcute de tipul respectiv – care să

constituie o ludografie (echivalentul unei bibliografii dar în care în loc de cărţi ne vom referi la jocuri) şi care să fie prezentate pe scurt (cele reprezentative), punând accentul pe deosebiri şi stabilind astfel un nucleu comun prin care să se definească acest tip de jocuri.

- se stabilesc care trăsături pot fi implementate în cadrul proiectului şi la care trebuie să se renunţe, fie datorită complexităţii, fie datorită volumului prea mare al muncii de implementare;

55..11..22 CCoommppoorrttaarreeaa mmiinniimmaallăă aa jjooccuulluuii

Page 157: Programare Orientata Pe Obiecte

156

Se are în vedere ca jocul să accepte intrări de la tastatura şi/sau mouse. Să prezinte o interfaţă grafică care să poată fi scalată într-o fereastră de

tip Windows şi să accepte modurile grafice disponibile în Windows.(nu se va lucru cu BGI).

Se va avea în vedere producerea unei animaţii de calitate fără pâlpâiri ale ecranului şi care să respecte legile bunului simţ.

Se vor folosi bitmapuri care vor fi integrate în resursele aplicaţiei vizuale, eventual este permis folosirea unor biblioteci specializate de încărcare a imaginilor.

Se vor integra un minim de efecte sonore prin utilizarea wav- urilor disponibile în Windows sau a unora disponibile gratis pe internet.

Parametrii de configurare şi datele referitoare la scorurile record obţinute vor fi păstrate şi reîncărcate din fişiere.

Fiecare joc va avea un meniu standard în care va fi inclus un fişier de ajutor şi o opţiune despre.

Se va avea în vedere un engine de joc care să consume cât mai puţine resurse pentru ca jocul să poată fi jucat în configuraţii cât mai minimale, eventual se vor stabili prin meniuri diferite opţiuni de grafică, sau configurare care să reducă resursele alocate.

55..11..33 DDiirreeccţţiiii ddee ddeezzvvoollttaarree aa jjooccuulluuii::

Se va avea în vedere în primul rând creştere play-abilităţii jocului, anume

comenzile să fie executate rapid şi fidel. În al doilea rând, ca direcţie de dezvoltare, se va urmări adăugarea de noi

funcţionalităţi elementelor de joc, de exemplu nivele suplimentare, bonusuri noi, acţiuni noi ale utilizatorului odată cu trecere la noi nivele sau achiziţionarea de bonusuri.

În al treilea rând se va urmări creşterea calităţii şi a esteticii elementelor grafice şi dacă se poate şi a celor audio.

În al patrulea rând se vor crea scenarii cât mai atractive, pentru că se ştie că un scenariu bun dă mai mult sens jocului, chiar dacă acesta nu are o grafică prea grozavă sau se repetă aceeaşi şi aceeaşi paşi.

În al cincilea rând îmbunătăţirea strategiilor jocului – prin creare unor IA mai evoluate.

55..11..44 SSttuuddiiuull uunneeii lluuddooggrraaffiiii şşii ssttaabbiilliirreeaa ccoommpplleexxiittăăţţiiii

Se va căuta, sau se vor cerceta din jocurile de gen propuse, pentru a se

vedea ce elemente vehiculează acestea, ce este nou la unele jocuri. Dacă studentul nu are cunoştinţă despre aceste jocuri le va încerca, jucând cel puţin 10 minute jocul vizat, sau dacă are experienţă va expune în ludografie jocurile de gen experimentate, sărind peste altele mai obscure. În final după ce şi-a făcut o opinie despre elementele din ludografie va expune diferenţele, respectiv nucleul comun al acestor jocuri şi ce anume poate fi implementat în cadrul proiectului sau este capabil să implementeze în cadrul proiectului. Eventual va sugera elemente noi, inovative în concepţie jocurilor de genul respectiv.

Page 158: Programare Orientata Pe Obiecte

157

5.2 Laborator de proiect 2.

55..22..11 SSttrruuccttuurriillee ddee ddaattee şşii oobbiieecctteellee jjooccuulluuii În acest laborator echipele de studenţi vor lucra la crearea structurilor primare

ale jocurilor, după ce şi-au făcut o idee despre ce trebuie inclus neapărat. Astfel sa va defini o clasă globală,adică care să conţină toate elementele jocului. Fiecare sub-element al jocului va fi declarat ca obiect al clasei globale. Acest

sub-element poate fi în funcţie de joc unul din următoarele: - la jocul de tenis – mingea, paleta, cărămida, bonusul, suprafaţa de joc, caseta de

afişat scorul; - la jocul de tetris – blocul de construcţie care cade, suprafaţa de joc, tabela de

scor, tabela ce anunţă piesa următoare; - la jocul gen dyna – omuleţul care explorează labirintul , bomba, explozia,

bonusul, monştrii, peretele din labirint, tabela de scor, tabela de stare a bonusului;

- la jocul genul Arcade Shooter - nava, proiectilul, explozia, navele inamice sau meteoriţii, bonusurile, tabela de scor, tabela de stare – de nivel. La toate jocurile va fi o structură în care se vor păstra datele de configurare

specifică fiecărui joc în parte, o structură generală în care se va păstra numele jucătorului, scorul obţinut, durata jocului şi data când s-a început jocul.

Dacă este permisă salvarea jocului în mijlocul acestuia se va specifica şi o

structură prin care să se salveze şi să se reia jocul dorit, din starea în care s-a rămas. Structura va fi demonstrativă, de abia în starea finală se va putea declară o structură general valabila.

Toate structurile definite vor fi flexibile ele putând fi modificate în funcţie de

cum evoluează crearea jocului. În acest laborator se va pune la punct declararea, structurilor şi a claselor, lucrul

cu fişierele, declararea funcţiilor membre. Se va lucra cu Visual Studio – creându-se o aplicaţie gen SDI (Single Document

Interface) se va configura meniul jocului, se vor declara variabilele, funcţiile membre.

55..22..22 CCrreeaarree ddee ttiippuurrii pprroopprriiii ddiinn ttiippuurrii vveecchhii Atunci când se doreşte o personalizare a unor tipuri de date deja definite pentru a

fi mai sugestivă programarea se foloseşte cuvântul cheie typedef cu următoarea sintaxă: typedef tip_vechi nume_tip_nou; exemplu:

Page 159: Programare Orientata Pe Obiecte

158

typedef int numar; numar n1,k,a;

55..22..33 CCrreeaarreeaa ddee ttiippuurrii nnooii –– ssttrruucctt O altă modalitate de creere de tipuri personale este crearea unei grupări de tipuri,

de date simple într-o structură. Vom folosi pentru aceasta cuvântul cheie struct cu următoarea sintaxă:

struct numestructura{ tip1 var1; tip2 var2; … tipn varn,varnn; }; exemplu: struct persoana{ char nume[50]; int varsta; char cnp[20]; char adresa[100]; float salariu; };

55..22..44 CCrreeaarree uunneeii eennuummeerrăărrii –– eennuumm Atunci când dorim să avem o mulţime de elemente a cărei constituenţi să le dăm

un nume vom folosi enumerările care au următoare sintaxă: Enum numeenumerare{element1,element2,element3,…elementn}; Putem folosi enumerarea astfel> numeenumerare p; … p=elementk; … if (p= = elementm) {…}

55..22..55 CCrreeaarreeaa ddee ccllaassee –– ccllaassss Definirea unei clase se face astfel: class nume_clasa{

Page 160: Programare Orientata Pe Obiecte

159

tip1 data1; tip2 data1[10]; … tipn datan; specificator1: nume_clasa(…); ~nume_clasa(…); tip1 functie1(…); specificator2: tip2 functie2(…); … tipn functien(…):

} [obiect1],[obiect2…]; Aceasta este structura generală de definire a unei clase. Se observă cuvântul

cheie class de la început, acesta se va regăsi în toate definiţiile de clase. Specificator1…n sunt specificatori de acces care stabilesc accesul la membrii

clasei cei care se găsesc după el fiind afectaţi, până ce apare un nou specificator de acces.

nume_clasa(…) este o funcţie constructor a clasei, se apelează automat când se declară un obiect de clasa respectivă.

~nume_clasa(…) este o funcţie destructor a clasei, se apelează când s-a terminat lucrul cu obiectul definit, pentru a elibera memoria.

Tipk functiek(…) este o funcţie membru al clasei. După ce s-a definit clasa trebuie definite şi funcţiile membre. În general pentru a

accesa orice membru al unei clase pentru definire se foloseşte operatorul de specificare a domeniului :: . Exemplu:

tipk nume_clasa::functiek(…) { // scriem continutul functiei return tipk; }

55..22..66 LLuuccrruull ccuu ffiişşiieerreellee Pentru a lucra cu fişierele vom folosi intrările – ieşirile standard pe bază de

streamuri din C++. Vom folosi fişiere text pentru a salva configurările şi fişiere de tip dată cu acces

aleatoriu pentru a salva încărca starea jocului. Mai putem folosi biblioteci pentru încărcarea de imagini însă acestea în general

îşi gestionează singurele accesul la fişiere. Avem următoarele tipuri de tipuri de streamuri fişier:

Page 161: Programare Orientata Pe Obiecte

160

- ifstream – pentru scriere în fişier; - ofstream - pentru citire din fişier; - fstream – pentru scriere şi citire din fişier; Deschidem streamul asociat fişierului cu funcţia open() ce are următorul

prototip: void open(const char *numefisier,int mod,int acces);

În general vom avea nevoie doar de numele fişierului putând scrie doar: ofstream iesire; iesire.open(”test.txt ”);

Pentru a scrie/citi date din fişier vom folosi operatorii de extracţie – inserţie

cunoscuţi de la lucrul cu cin, cout numai că în locul acestora vom avea numele streamurilor fişier.

Pentru a deschide în mod binar un stream de fişier vom utiliza specificatorul

ios::binary. Exemplu: ofstream iesire; iesire.open(”aut.txt”,ios::out|ios::binary); iesire.put(’a’); iesire.close(); Pentru a scrie date dintr-o locatie de memorie folosim: istream &read(unsigned char *buf,int ncitit); ostream &write(const unsigned char *buf,int nscrisi); Pentru a ne poziţiona în cadrul fişierelor deschise binar folosim: istream &seekg(streamoff offset,seek_dir origine); ostream &seekp(streamoff offset,seek_dir origine); Prin combinarea acestor funcţii se poate scrie sau citi orice dată dorită din şi în

fişier.

Page 162: Programare Orientata Pe Obiecte

161

5.3 Laborator de proiect 3.

55..33..11 DDeesseennaarreeaa oobbiieecctteelloorr,, lluuccrruull ccuu ffuunnccţţiiiillee ggrraaffiiccee

Ne propunem să desenăm obiectele care vor constitui elementele jocului. Unele imagini vom căuta să le generăm, altele le vom desena cu uneltele oferite

de editorul resurse,anume editorul de bitmapuri disponibil în Visual C++. Altele le vom crea prin îmbinarea primitivelor grafice puse la dispoziţie de Visual C++.

Deci avem trei modalităţi de abordare: - generarea imaginilor din puncte şi primitive cu salvarea lor sub forma de

bitmapuri; - crearea sau importarea de imagini prin editorul de resurse Visual C++. - cearea de imagini şi interfeţe prin primitivele grafice puse la dispoziţie de Visual

C++. Cea mai eficientă soluţie şi care cere cel mai puţin de lucru este crearea

imaginilor în afara mediului Visual C++, folosind editoare şi unelte specializate de generare ale imaginilor. Alta ar fi lucrul cu Visual C++ pentru generarea unor imagini care vor fi salvate sub formă de bitmapuri şi reutilizate. Alta ar fi crearea de imagini prin editorul de resurse o soluţie nu prea strălucită dar disponibilă.

Pentru a defini o resursă proprie de tip bitmap folosim următoarea secvenţă de

paşi:

1. Având deja creat fişierul program – vom seta modul de vizualizare pe resource view, în partea stângă a ecranului;

2. Vom adăuga un bitmap la resurse prin clic dreapta pe nodul rădăcină, respectiv accesând meniul insert;

3. Va apare o fereastră din care vom selecta Bitmap – şi vom apăsa New; 4. Va fi creată o imagine bitmap fără conţinut; 5. Vom face apel la sub-meniul Open din meniul File din meniul bară principal al

mediului vizual de programare; 6. Setăm tipul fişierelor ce vor fi deschise ca fiind - Image Files -. 7. Căutăm şi încărcăm imaginea dorită având grijă să fie setată bitmapul fără

conţinut din arborele de resurse. 8. În locul imaginii necompletate va apărea imaginea dorită.

Această imagine poate fi modificată sau se poate crea o imagine pornind de la

zero folosind meniul de desenare:

Page 163: Programare Orientata Pe Obiecte

162

Abordând sistematic meniul de desenare fiecare rând de la stânga la dreapta de

sus în jos avem: - opţiunea de decupare a unui dreptunghi; - opţiunea de decupare după un contur oarecare; - opţiunea de copiere a unei culori din imagine; - opţiunea de selectare a pointerului de ştergere; - opţiunea de umplere a unui contur folosind culoarea selectată; - opţiunea de mărire a imaginii; - opţiunea de selectare a pointerului creion desenează câte un punct odată; - opţiunea de selectare a pointerului pensulă desenează un grup de puncte de o

anumită configuraţie odată; - opţiunea de selectare a pointerului spray; - opţiunea de trasare a liniei; - opţiunea de trasare a unei curbe; - opţiunea de desenare a unui text; - opţiunea de desenare a unui dreptunghi – gol, plin cu margine, plin fără margine; - opţiunea de desenare a unui dreptunghi rotunjit– gol, plin cu margine, plin fără

margine; - opţiunea de desenare a unei elipse – gol, plin cu margine, plin fără margine;

Page 164: Programare Orientata Pe Obiecte

163

Mai jos este o casetă de sub-opţiune pentru alege grosimea liniei, forma pensulei, extinderea spray-ului, transparenţa fundalului textului etc.

Iar sub aceasta este caseta din care se pot alege culorile. Pentru mai multe culori

accesaţi meniul Image, apoi Adjust Colors…. unde se poate seta în detaliu culoarea curentă.

Cealaltă opţiune ar fi desenare folosind funcţiile:

- primitive tip linie: o MoveTo/ LineTo – trasarea unei linii; o Arc/ ArcTo/ Angle Arc – trasarea unui arc de cerc; o PolyDraw/ Polyline /PolyPolyline / PolylineTo – trasare unei linii

poligonale; o PolyBezier / PolyBezierTo – trasarea unei curbe Bezier;

- primitive tip figură umplută: o Chord – pentru o regiune eliptică; o DrawFocusRect – pentru un dreptunghi cu aspect focalizat; o Ellipse – desenează o elipsă; o Pie – desenează o regiune eliptică mărginită de două raze; o Polygon / PolyPolygon – desenează un polygon plin sau un set de

poligoane; o Rectangle – desenează un dreptunghi plin; o RoundRect – desenează un dreptunghi având colţuri rotunjite;

- de afişare a textului: o TextOut / ExTextOut – pentru afişarea unui text; o TabbedTextOut – afişează un text identat cu taburi; o DrawText – desenează un text încadrat într-o regiune dreptunghiulară; o GetTextExtent / GetOutputTextExtent /GetTabbedTextExtent –

returnează dimensiunile textului afişat; o GetTextAlign / SetTextAlign – stabileşte alinierea textului; o GetBkColor / SetBackColor – se referă la culoarea de fundal a textului; o GetTextColor / SetTextColor – se referă la culoarea efectivă a textului; o GetBkMode / SetBKMode – se referă la stilul în care este afişat fundalul;

- de afişare a bitmapurilor: o PatBlt / BitBlt / StretchBlt – pentru a afişa un bitmap din memorie; o GetPixel / SetPixel – pentru a afişa , respectiv citi un pixel de pe ecran; o Floodfill / ExtFloodFill – pentru a umple un contur cu o anumită culoare; o MaskBlt / PlgBlt – alte metode de afişare a unui bitmap;

În combinaţie cu aceste sunt clasele:

- CPen pentru specificarea peniţei; - CBrush pentru specificarea pensulei; - CRect pentru specificarea unei regiuni rectangulare; - CPoint pentru specificarea coordonatelor unui punct; - CFont pentru a specifica parametrii unui font; - CBitmap pentru a lucru cu o imagine de tip bitmap;

Page 165: Programare Orientata Pe Obiecte

164

- CRgn – pentru a specifica o regiune din fereastră; - CPalette – pentru a specifica o paletă de culori; - CDC – pentru specificare generală a unui dispozitiv de context; - CPaintDC – dispozitivul principal de context de desenare; - CClientDC – dispozitiv secundar de desenare apelabil din oricare metoda a

clasei principale; - CWindowDC – dispozitiv de context asociat unei ferestre; - CMetaFileDC – dispozitiv de context asociat unui metafişier;

Page 166: Programare Orientata Pe Obiecte

165

5.4 Laborator de proiect 4.

55..44..11 AAnniimmaarreeaa eelleemmeenntteelloorr ddee jjoocc În acest laborator se va aborda problema unei animaţii folosind obiecte statice,

folosirea unor fişiere video tip .avi., introducerea şi integrarea de suport audio în aplicaţie.

55..44..22 AAnniimmaaţţiiaa uunnoorr oobbiieeccttee ssttaattiiccee::

În laboratorul trecut s-a abordat generarea unor imagini statice. Pentru a le afişa

şi pentru a da senzaţia de mişcare trebuie folosită o metodă care la intervale regulate de timp, extrem de scurte, dar îndeajuns de lungi ca ochiul să observe diferenţa, să afişăm o nouă imagine, sau aceeaşi imagine dar la coordonate diferite. În acest sens avem nevoie de o funcţie care să fie apelată la intervale constante de timp. Acest lucru se poate realiza folosind timere – care generează la intervale de timp specificate un mesaj WM_TIMER căruia îi pot fi asociate funcţii. Precizia unui timer este de o milisecundă. Dacă de exemplu vrem o animaţie cu 100 de cadre timerul va avea asociat o durata de 10 milisecunde. Pe cât de mare este numărul de cadre pe secundă cu atât se face apel la mai multe resurse, mai mult timp de procesare. Rezonabil ar fi cam 30 de cadre pe secundă, deci o constantă de timp de aprox. 30 de milisecunde.

Declararea unui timer: UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK

EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) ); ,unde : - nIDEvent este ID-ul asociat timerului ; - nElapse este intervalul de timp în milisecunde la care este apelat timerul ; - următorul parametru se poate seta cu 0 ;

Exemplu de folosire a unui timer :

void CMainFrame::OnStartTimer() { m_nTimer = SetTimer(1, 2000, 0);//crează timerul } void CMainFrame::OnStopTimer() { KillTimer(m_nTimer); // distruge timerul } void CMainFrame::OnTimer(UINT nIDEvent) //corpul funcţiei associate //timerului { MessageBeep(0xFFFFFFFF); //functia asociată timerului va emite un //beep // Call base class handler.

Page 167: Programare Orientata Pe Obiecte

166

CMDIFrameWnd::OnTimer(nIDEvent)//timerul va fi recirculat }

Se poate crea un singur timer, toate elementele având normalizare temporală în

limita constantei de timp a timerului. În funcţia de timer se vor introduce bucăţile de cod ce vor fi animate , având

specificate un contor de evoluţie temporară – care va putea genera fie poziţii succesive pe o traiectorie, fie următorul cadru al animaţiei. Se va constata pe parcurs că intrările de la utilizator nu specifică fiecare pas al animaţiei ci doar iniţiază o secvenţă de animaţie. Astfel în cazul mingii la tenis utilizatorul nu poate acţiona decât asupra paletei mingea fiind animată în mod continuu, paleta va fi mişcată nu direct de utilizator,ci cu anumite efecte inerţiale. La mişcarea omuleţului utilizatorul va preciza doar începutul şi sfârşitul deplasării acestuia nu şi cum se mişcă efectiv. La fel ca în cazul mingii monştrii vor fi animaţi în mod continuu. La fel şi cu meteoriţii la Shooter. Un caz mai interesant este o mişcare semiautomată a cărămizilor din tetris, căderea va fi automată dar rotirea va fi controlată de utilizator.

55..44..33 EEvviittaarreeaa ppââllppââiirriiii iimmaaggiinniiii

Pentru a face să dispară acest fenomen este indicat ca tot ce se desenează şi se

animează la un moment dat să fie desenat într-un bitmap, când desenarea obiectelor s-a încheiat, bitmapul va fi afişat pe ecran. Preferabil ca întreaga suprafaţă de joc să fi afişată printr-un bitmap. Această tehnică se numeşte double – buffering – în engleză, şi nu are o traducere echivalentă, simplă în română, dar semnificaţia lui în mare este cea de mai sus.

55..44..44 AAnniimmaaţţiiaa ppee bbaazzăă ddee aavvii--uurrii

Visual C++ are o bibliotecă care face apel la funcţii multimedia. Un set de astfel

de funcţii şi structuri este destinat pentru lucrul cu fişierele AVI. Pentru a lucra cu un fişier avi putem folosi următoare secvenţă de cod:

// LoadAVIFile – incarca si deschide fisiere AVI // // szfile – numele fisierului // hwnd – pointer gen indice al ferestrei // VOID LoadAVIFile(LPCSTR szFile, HWND hwnd) { LONG hr; PAVIFILE pfile; AVIFileInit(); // initializeaza biblioteca AVI hr = AVIFileOpen(&pfile, szFile, OF_SHARE_DENY_WRITE, 0L); //deschide AVI if (hr != 0){ ErrMsg("Nu am putut deschide %s", szFile);

Page 168: Programare Orientata Pe Obiecte

167

return; } // // Aici se pun functiile care interactionează cu structura AVI // AVIFileRelease(pfile); // se inchide fisierul AVI AVIFileExit(); // se eliberează biblioteca AVI

} Pentru a deschide nişte streamuri de prelucrare AVI folosim codul:

void InsertAVIFile(PAVIFILE pfile, HWND hwnd, LPSTR lpszFile) { int i; gcpavi = 0; //se deschid toate streamurile disponibile for (i = gcpavi; i < MAXNUMSTREAMS; i++) { gapavi[i] = NULL; if (AVIFileGetStream(pfile, &gapavi[i], 0L, i - gcpavi) != AVIERR_OK) break; if (gapavi[i] == NULL) break; } // Display error message-stream not found. if (gcpavi == i) { ErrMsg("Fisierul %s nu a fost gasit!", lpszFile); if (pfile) // daca fisierul a fost deschis acesta este inchis AVIFileRelease(pfile); return; } else { gcpavi = i - 1; }

Pentru procesarea streamurilor în continuare se foloseşte următoarea secvenţă:

void StreamTypes(HWND hwnd) { AVISTREAMINFO avis; LONG r, lHeight = 0; WORD w; int i; RECT rc; // Parcurgem toate streamurile for (i = 0; i < gcpavi; i++) { AVIStreamInfo(gapavi[i], &avis, sizeof(avis));

Page 169: Programare Orientata Pe Obiecte

168

if (avis.fccType == streamtypeVIDEO) { // Aici se fac prelucrarile secventelor video } else if (avis.fccType == streamtypeAUDIO) { // Aici se fac prelucrarile secventelor audio } else if (avis.fccType == streamtypeTEXT) { // Aici se fac prelucrarile secventelor text } } }

Sunt disponibile următoarele funcţii de lucru cu AVI-uri :

- AVIBuildFilter - AVIClearClipboard - AVIFileAddRef - AVIFileCreateStream - AVIFileEndRecord - AVIFileExit - AVIFileGetStream - AVIFileInfo - AVIFileInit - AVIFileOpen - AVIFileReadData - AVIFileRelease - AVIFileWriteData - AVIGetFromClipboard - AVIMakeCompressedStream - AVIMakeFileFromStreams - AVIMakeStreamFromClipboard - AVIPutFileOnClipboard - AVISave - AVISaveOptions - AVISaveOptionsFree - AVISaveV - AVIStreamAddRef - AVIStreamBeginStreaming - AVIStreamCreate - AVIStreamEndStreaming - AVIStreamFindSample - AVIStreamGetFrame - AVIStreamGetFrameClose - AVIStreamGetFrameOpen

Page 170: Programare Orientata Pe Obiecte

169

- AVIStreamInfo - AVIStreamLength - AVIStreamOpenFromFile - AVIStreamRead - AVIStreamReadData - AVIStreamReadFormat - AVIStreamRelease - AVIStreamSampleToTime - AVIStreamSetFormat - AVIStreamStart - AVIStreamTimeToSample - AVIStreamWrite - AVIStreamWriteData - CreateEditableStream - EditStreamClone - EditStreamCopy - EditStreamCut - EditStreamPaste - EditStreamSetInfo - EditStreamSetName

55..44..55 LLuuccrruull ccuu ffiişşiieerree aauuddiioo Apelarea funcţiilor audio va fi realizată tot în interiorul funcţii temporizate. Cea mai simplă abordare este folosirea funcţiei având prototipul: BOOL PlaySound( LPCSTR numefisier, HMODULE modul_deschidere,

DWORD indicatori_de_redare ); ,unde: - modul_deschidere – este un pointer spre o resursă executabilă care redă

fişierul în mod obişnuit este NULL; - indicatori de redare specifică modul cum va fi redat fişierul audio de tip WAV

opţiunea cea mai utilizată este : SND _ASYNC || SND_FILENAME. De exemplu pentru a reda fişierul „explozie.wav” fără a aştepta terminarea

acestuia (opţiunea asincronă) se utilizează următoarea apelare a funcţiei: PlaySound(“explozie.wav ”, NULL, SND _ASYNC || SND_FILENAME); Mai sunt şi alte opţiuni de redare a fişierelor audio: o abordare fiind controlul

direct al redării prin împărţirea pe bucăţi a fişierului audio, cealaltă metodă este doar specificarea parametrilor de redare, încredinţarea gestionării fluxului fiind acordată unui driver de redare.(Biblioteca MCI).

Abordarea MCI pentru fişiere audio lungi este cea mai uşor de implementat.

Page 171: Programare Orientata Pe Obiecte

170

Un exemplu de abordare MCI este următorul :

DWORD playWAVEFile(HWND hWndNotify, LPSTR lpszWAVEFileName) { UINT wDeviceID; DWORD dwReturn; MCI_OPEN_PARMS mciOpenParms; MCI_PLAY_PARMS mciPlayParms;

// Se deschide dispozitivul MCI prin specificarea numelui //fisierului si a dispozitivului multimedia mciOpenParms.lpstrDeviceType = "waveaudio"; mciOpenParms.lpstrElementName = lpszWAVEFileName; if (dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &mciOpenParms)) { //s-a produs o eroare la deschiderea fisierului return (dwReturn); } // Dispozitivul s-a deschis fara erori extragem ID-ul wDeviceID = mciOpenParms.wDeviceID; // Redam fisierul prin intermediul dispozitivului multimedia mciPlayParms.dwCallback = (DWORD) hWndNotify; if (dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms)) { mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL); return (dwReturn); } return (0L);

}

Page 172: Programare Orientata Pe Obiecte

171

5.5 Laborator de proiect 5.

55..55..11 GGeessttiioonnaarreeaa iinntteerrffeeţţeeii ccuu uuttiilliizzaattoorruull –– ttaassttaattuurrăă şşii mmoouussee Pentru a interacţiona cu jocul utilizatorul trebuie să folosească un dispozitiv de

intrare. Cel mai utilizat este mouse-ul, iar pentru comenzi suplimentare tastatura. În unele jocuri avansate se mai folosesc joystickul cu sau fără feedback sau gamepad-ul sau volane cu feedback. În cazul jocurilor pe care le creem vom apela mai des la tastatură decât la mouse.

Lucrul cu mouse-ul şi tastatura presupune gestionarea unor mesaje şi asocierea lor cu nişte funcţii răspuns.

Pentru mouse avem următoarele mesaje:

- WM_LBUTTONDBLCLK – trimis dacă s-a făcut dublu-clic cu primul buton al mouselui;

- WM_LBUTTONDOWN – trimis dacă s-a apăsat primul buton al mouseului; - WM_LBUTTONUP – trimis dacă s-a eliberat al doilea buton al mouselui; - WM_MBUTTONDBLCLK – trimis dacă s-a făcut dublu clic pe butonul din

mijloc al mouseului; - WM_MBUTTONDOWN – trimis dacă s-a apăsat butonul mijlociu al mouselui; - WM_MBUTTONUP – trimis dacă s-a eliberat butonul mijlociu al mouseului; - WM_MOUSEMOVE – trimis dacă s-a deplasat mouseul; - WM_MOUSEWHEEL – trimis dacă s-a rotit rotiţa de derulare a mouseuui; - WM_RBUTTONDBLCLK – trimis dacă s-a făcut dublu clic pe butonul doi al

mouselui (butonul din dreapta în mod standard); - WM_RBUTTONDOWN – trimis dacă s-a apăsat butonul al doilea al mouseului; - WM_RBUTTONUP – trimis dacă s-a eliberat butonul al doilea al mouseului;

Asocierea unui mesaj cu o funcţie se face prin adăugarea unui gestionar

(handler) de evenimente. Aceasta se poate face prin clic dreapta în clasa fereastră pe clasa cu numele CxxxView şi alegerea opţiunii Add Windows Message Handler de aici doar transferăm mesajul dorit din lista din stânga în cea din dreapta şi apoi facem dublu clic pe el, mediul de programare ne va poziţiona în dreptul funcţiei asociate unde vom scrie acţiunea dorită.

Pentru aplicaţii simple se pot trata separat fiecare eveniment generat de mouse, dar vom observă că atunci când lucrurile se complică este necesar că fiecare din funcţiile asociate să formateze într-un mod comod datele ce parvin de la mouse şi să le transmită mai departe unei funcţii unice de gestionare a mouse-ului – de exemplu MouseWork(int mb,int mx,int my,int aa,int kf) în care mb semnifică starea butoanelor de mouse, mx,my coordonatele pointerului, aa deplasarea rotiţei de scroll, kf starea butoanelor funcţionale CTRL, SHIFT, ALT. Motivul abordării mouselui în acest fel este că nu toţi parametrii sunt în general actualizaţi de aceea starea lor trebuie reţinută, şi procesată atunci când apare o circumstanţă semnificativă pentru aplicaţie.

Acestor mesaje le sunt asociate următoarele funcţii de gestionare: afx_msg void OnXButtonDblClk( UINT nFlags, CPoint point ); afx_msg void OnXButtonDown( UINT nFlags, CPoint point );

Page 173: Programare Orientata Pe Obiecte

172

afx_msg void OnXButtonUp( UINT nFlags, CPoint point ); afx_msg void OnMouseMove( UINT nFlags, CPoint point ); afx_msg BOOL OnMouseWheel( UINT nFlags, short zDelta, CPoint pt ); Unde X poate fi L,M sau R, nFlags reprezintă nişte indicatori de stare analizabili prin constantele:

• MK_CONTROL - Setat dacă tasta CTRL este apăsată;

• MK_LBUTTON - Setat dacă primul buton al mouseului este apăsat;

• MK_MBUTTON - Setat dacă butonul din mijloc al mouseului este apăsat;

• MK_RBUTTON - Setat dacă butonul al doilea al mouseului este apăsat;

• MK_SHIFT - Setat dacă tasta SHIFT este ţinută apăsat;

Point – reprezintă poziţia pointerului mouseului; zDelta – reprezintă deplasarea unghiulară a rotiţei de mouse.

55..55..22 GGeessttiioonnaarreeaa mmeessaajjeelloorr ddee ttaassttaattuurrăă Pentru gestionarea mesajelor de tastatură se face apel la următoarele mesaje:

- WM_CHAR – tratează primul tip de intrări de tastatură fiind generat la apăsarea unei taste de acest tip;

- WM_KEYDOWN – generat la apăsarea şi ţinerea apăsată a unei taste funcţionale;

- WM_KEYUP – generat la eliberarea unei taste funcţionale;

Se observă că se face o diferenţă între taste – tastele care returnează un caracter ASCII generează în special mesajul WM_CHAR, pe când tastele funcţionale numai mesajele WM_KEYDOWN şi WM_KEYUP. Aceste mesaje au asociate funcţiile:

afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags ); afx_msg void OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags ); La OnChar – nChar specifică codul ASCII asociat caracterului generat de tasta

respectivă ; La OnKeyDown,OnKeyUp – nChar specifică codul virtual al tastei funcţionale ; nRepCnt – numărul de repetări ale tastei ca urmare a ţinerii apăsate a unei taste; nFlags –sunt indicatori ale tipului de tasta (pentru OnKeyDown, OnKeyUp) :

- biţii de la 0-7 specifică codul tastei specificat prin standardul OEM ; - bitul 8 – dacă este setat specifică o tastă extinsă, în cazul tastelor funcţionale ; - bitul 13 – indică dacă tasta ALT este apăsată ;

Page 174: Programare Orientata Pe Obiecte

173

- bitul 14 – indică o stare anterioară a tastei – 1 dacă tasta era apăsată înaintea apelării şi 0 dacă tastă era liberă ;

- bitul 15 – indică o stare de tranziţie -1 dacă tasta este eliberată, 0 dacă este apăsată ; Pentru OnChar nFlags are următoarea semnificaţie :

- biţii 0-15 - specifică numărul de repetiţii ale tastei prin ţinerea ei apăsată; - biţii 16-23 - specifică codul de scanare al tastei definit de OEM ; - bitul 24 – specifică o tastă extinsă ; - ultimii trei biţi (29,30,31) au aceeaşi semnificaţie ca în cazul anterior ;

Se mai poate lucra şi cu acceleratorii care sunt una din resursele puse la

dispoziţie de Visual C++. Pe lângă aceste mijloace mai sunt o serie de funcţii de gestionare a tastaturii în biblioteca Win32. Pentru o gestionare completă a tastaturii este recomandabil ca tuturor tastelor să li se asocieze un vector în care printr-o valoare 1 să se specifice că tasta a fost apăsată şi este ţinută apăsată şi cu 0 că tasta a fost eliberată. În următoarea secvenţă de cod se prezintă o gestionare elementară a tastaturii cu posibilitatea afişării tastelor curent apăsate pe ecran.

void CSs05View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default char s[100]; keys[nChar]=1; //tasta este apasata int i,j=0; CClientDC dc(this); //stergem zona de afisare a starii tastaturii dc.Rectangle(0,0,100,500); //afisam tastele care sunt apasate in momentul de fata for (i=1;i<=255;i++) if (keys[i]==1) { sprintf(s,"%d",i); dc.TextOut(10,10+(j%20)*15,s); j++; } CView::OnKeyDown(nChar, nRepCnt, nFlags); } void CSs05View::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { // TODO: Add your message handler code here and/or call default char s[100]; //tasta este eliberata keys[nChar]=0; int i,j=0; CClientDC dc(this); //stergem suprafata de afisare dc.Rectangle(0,0,100,500);

Page 175: Programare Orientata Pe Obiecte

174

// afisam codul tastelor ce raman afisate for (i=1;i<=255;i++) if (keys[i]==1) { sprintf(s,"%d",i); dc.TextOut(10,10+(j % 20)*15,s); j++; } CView::OnKeyUp(nChar, nRepCnt, nFlags); } Vectorul de care vorbeam este keys[]. Se observă că se accepta apăsare a mai

mult de două taste în acelaşi timp putându-se ajunge până la 6 - 7 taste apăsate simultan, acest număr variază în funcţie de producătorii de tastaturi.

Veţi vedea în jocuri că este necesar să se poată da mai multe comenzi simultan de la tastatură şi atunci când se joacă în mod Single Player(un singur jucător), şi atunci când joacă două persoane una împotriva alteia, astfel încât comenzile să nu se bruieze reciproc.

Este important de ştiut numele codurilor virtuale ale tastelor. În lista de mai jos

sunt codurile cele mai des utilizate : VK_BACK 08 BACKSPACE tasta VK_TAB 09 TAB tasta VK_CLEAR 0C CLEAR tasta VK_RETURN 0D ENTER tasta VK_SHIFT 10 SHIFT tasta VK_CONTROL 11 CTRL tasta VK_MENU 12 ALT tasta VK_PAUSE 13 PAUSE tasta VK_CAPITAL 14 CAPS LOCK tasta VK_ESCAPE 1B ESC tasta VK_SPACE 20 SPACEBAR VK_PRIOR 21 PAGE UP tasta VK_NEXT 22 PAGE DOWN tasta VK_END 23 END tasta VK_HOME 24 HOME tasta VK_LEFT 25 LEFT ARROW tasta VK_UP 26 UP ARROW tasta VK_RIGHT 27 RIGHT ARROW tasta VK_DOWN 28 DOWN ARROW tasta VK_SELECT 29 SELECT tasta VK_SNAPSHOT 2C PRINT SCREEN tasta for Windows 3.0 and later VK_INSERT 2D INS tasta VK_DELETE 2E DEL tasta VK_HELP 2F HELP tasta VK_0 30 0 tasta VK_1 31 1 tasta

Page 176: Programare Orientata Pe Obiecte

175

VK_2 32 2 tasta VK_3 33 3 tasta VK_4 34 4 tasta VK_5 35 5 tasta VK_6 36 6 tasta VK_7 37 7 tasta VK_8 38 8 tasta VK_9 39 9 tasta VK_A 41 A tasta VK_B 42 B tasta VK_C 43 C tasta VK_D 44 D tasta VK_E 45 E tasta VK_F 46 F tasta VK_G 47 G tasta VK_H 48 H tasta VK_I 49 I tasta VK_J 4A J tasta VK_K 4B K tasta VK_L 4C L tasta VK_M 4D M tasta VK_N 4E N tasta VK_O 4F O tasta VK_P 50 P tasta VK_Q 51 Q tasta VK_R 52 R tasta VK_S 53 S tasta VK_T 54 T tasta VK_U 55 U tasta VK_V 56 V tasta VK_W 57 W tasta VK_X 58 X tasta VK_Y 59 Y tasta VK_Z 5A Z tasta VK_NUMPAD0 60 Numeric keypad 0 key VK_NUMPAD1 61 Numeric keypad 1 key VK_NUMPAD2 62 Numeric keypad 2 key VK_NUMPAD3 63 Numeric keypad 3 key VK_NUMPAD4 64 Numeric keypad 4 key VK_NUMPAD5 65 Numeric keypad 5 key VK_NUMPAD6 66 Numeric keypad 6 key VK_NUMPAD7 67 Numeric keypad 7 key VK_NUMPAD8 68 Numeric keypad 8 key VK_NUMPAD9 69 Numeric keypad 9 key VK_MULTIPLY 6A Multiply key VK_ADD 6B Add key VK_SEPARATOR 6C Separator key

Page 177: Programare Orientata Pe Obiecte

176

VK_SUBTRACT 6D Subtract key VK_DECIMAL 6E Decimal key VK_DIVIDE 6F Divide tasta VK_F1 70 F1 tasta VK_F2 71 F2 tasta VK_F3 72 F3 tasta VK_F4 73 F4 tasta VK_F5 74 F5 tasta VK_F6 75 F6 tasta VK_F7 76 F7 tasta VK_F8 77 F8 tasta VK_F9 78 F9 tasta VK_F10 79 F10 tasta VK_F11 7A F11 tasta VK_F12 7B F12 tasta VK_F13 7C F13 tasta VK_F14 7D F14 tasta VK_F15 7E F15 tasta VK_F16 7F F16 tasta VK_F17 80H F17 tasta VK_F18 81H F18 tasta VK_F19 82H F19 tasta VK_F20 83H F20 tasta VK_F21 84H F21 tasta VK_F22 85H F22 tasta VK_F23 86H F23 tasta VK_F24 87H F24 tasta VK_NUMLOCK 90 NUM LOCK tasta

VK_SCROLL 91 SCROLL LOCK tasta Acum aveţi acces la marea parte a funcţiilor tastaturii şi mouselui. În

program nu veţi conecta direct o acţiune a elementului de joc manevrabil la tastă ci veţi construi o listă gen FIFO în care veţi pune comenzile urmând ca acestea să fie tratate ori în funcţia Timerului ori într-o funcţie tampon de gestionare a acţiunilor care să transmită timerului doar comenzi de animaţie.

Page 178: Programare Orientata Pe Obiecte

177

5.6 Laborator de proiect 6.

55..66..11 IInntteeggrraarreeaa eelleemmeenntteelloorr pprreecceeddeennttee În etapele precedente practic am realizat toate elementele esenţiale ale unui joc

dar nu le-am pus să lucreze împreună. Esenţialul într-un joc este ca un element să interacţioneze cu altul iar prin aceasta jucătorul să interacţioneze cu mediul virtual al jocului. Vom aborda problema exemplificând pentru fiecare joc în parte.

La jocul de tenis mingea trebuie să interacţioneze cu paleta şi cu cărămizile, atât paleta cât şi cărămizile vor genera o ciocnire perfect elastică respingând mingea. De asemenea minge mai interacţionează şi cu pereţii laterali producându-se de asemenea ciocniri elastice. Mingea nu trebuie să treacă prin paletă sau prin cărămizi decât dacă se doreşte expres acest lucru. În timp ce paleta rămâne intactă, în urma ciocnirilor cărămizile se sparg sau trec în altă stare. De asemenea bonusurile trebuie absorbite de paletă dacă aceasta stă în calea lor.

La jocul de tetris – la fiecare mişcare a elementului de construcţie se va verifica dacă elementul are libertate de mişcare. Dacă la căderea elementului se constată că acesta nu mai are loc unde să cadă, se va mai lăsa un interval de timp scurt pentru translaţii pe orizontală apoi obiectul va fi cimentat în zid şi altul va începe să cadă.

La jocul gen Dyna – la fiecare mişcare o omuleţului se va verifica dacă calea este liberă (nu este zid,nu este monstru,nu este bombă pusă). Dacă valul exploziei dă peste omuleţ sau mişcarea monstrului se intersectează cu cea a omuleţului se dă mesajul că omul a murit şi în acel moment se declanşează procedura de pierdere a unei vieţi sau de încheiere a jocului. Şi monştrii au interdicţii de a trece prin ziduri sau peste bombe, în afară de cazul în care abilităţile lor le permit acest lucru.

La jocul gen Arcade Shooter – proiectilele lansate de nava vor interacţiona cu meteoriţii. Meteoriţii vor interacţiona, dacă nu sunt distruşi şi se ciocnesc cu nava, cu nava. Proiectilele lansate de nave inamice, inclusiv navele inamice, în eventualitatea unor ciocniri vor interacţiona cu nava.

55..66..22 IIAA--uull jjooccuulluuii

Inteligenţa artificială asociată jocului în cazul de faţă este doar una elementară

sau redusă la minim. La tenis va trebui doar să examinăm nişte ciocnire şi unghiuri de deviaţie. La tetris doar o suprapunere a unei matrici peste alta. La Shooter doar o intersectare dintre proiectile, navă, nave şi meteoriţi. În schimb la Dyna, programarea mişcării monştrilor poate constitui o temă inepuizabilă de dezvoltare a IA-ului, dar pentru proiectul de faţă ne vom propune să realizăm nişte monştri nu prea isteţi, care nu fug după omuleţ, ci doar îi iau o viaţă dacă traiectoria lor se intersectează cu cea o omuleţului. Aceasta s-ar putea întâmpla fie că omuleţul este prins la colţ, fie că monstrul este mai rapid şi nu a putut fi evitat, fie din neatenţia sau nesăbuinţa jucătorului. Un algoritm simplu de mişcare haotică a monstrului ar fi ca acesta să parcurgă în linie dreaptă un culoar până întâlneşte un obstacol, apoi să o ia pe următorul drum neexplorat, disponibil, altfel doar să se întoarcă înapoi.

Page 179: Programare Orientata Pe Obiecte

178

Deci în mare parte vor apărea probleme de implementare a unor comportamente

şi efecte grafice corelate între ele şi nu probleme efective de programare a IA-ului.

55..66..33 CCoonnssttrruuccţţiiaa uunnuuii mmeenniiuu ddeesspprree Se observă că Visual C++ deja a construit o fereastră despre. Aceasta poate fi

accesată prin modul de gestionare a resurselor din ramura Dialog – apoi făcând clic pe IDD_ABOUTBOX.

Vom modifica iconul înlocuindu-l cu unul propriu, titlul ferestrei, cine a realizat

jocul, copyright ul jocului, ocazia ce a dus la creare jocului.

55..66..44 CCoonnssttrruuccţţiiaa uunnuuii mmeenniiuu ddee aajjuuttoorr Vom crea un meniu de ajutor pentru a da detalii despre joc, neapărat vom descrie

scopul jocului, strategii de îmbunătăţire a performanţei jucătorului, tastele asociate comenzilor şi semnificaţia lor, tipurile de bonusuri, de monştri, de nave inamice, de cărămizi.

Vom crea o nouă fereastră de dialog prin clic dreapta pe ramura dialog din

fereastră de resurse. Vom apăsa meniul Insert Dialog. Va apărea un dialog standard la care vom adăuga un control CRichEditCtrl pentru a afişa un fişier RTF care va conţine toate informaţiile de ajutor. Fişierul RTF va fi creat cu WordPad sau alte editoare de RTF-uri şi va fi încărcat cu următoarea secvenţă:

static DWORD CALLBACK MyStreamInCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { CFile* pFile = (CFile*) dwCookie; *pcb = pFile->Read(pbBuff, cb); return 0; } //pointer spre controlul de tip RichEdit extern CRichEditCtrl* pmyRichEditCtrl;

Page 180: Programare Orientata Pe Obiecte

179

// fisierul din care vom încărca conţinutul pt RichEdit CFile cFile(TEXT("ajutor.rtf"), CFile::modeRead); EDITSTREAM es; es.dwCookie = (DWORD) &cFile; es.pfnCallback = MyStreamInCallback; pmyRichEditCtrl->StreamIn(SF_RTF, es);

Această secvenţă va fi introdusă la iniţializare programului. La apelare meniului

de ajutor doar se va deschide fereastră, fişierul ajutor.rtf fiind deja încărca.

55..66..55 MMeenniiuurrii îînn lliimmbbaa rroommâânnăă Vom accesa ramura Meniu iar de acolo IDR_MAINFRAME vom face clic pe

fiecare element de meniu şi-l vom redenumi.

55..66..66 MMeenniiuull ssccoorr rreeccoorrdd Se va crea o opţiune de meniu de vizualizare scoruri record şi un dialog care va

apărea doar atunci când jucătorul a obţinut un scor record în care acesta îşi va introduce numele.

În fereastra de introducere a numelui va fi specificat scorul şi locul ocupat în clasament alături de caseta de editare în care îşi va introduce numele. După ce fereastra de înregistrare a scorului record va fi închisă, va apărea tabela cu scoruri record.

Scorurile record vor fi citite din fişier la intrarea în joc, vor fi modificate în timpul rulării jocului şi vor fi salvate la ieşirea din joc.

55..66..77 ÎÎnngghheeţţaarreeaa,, ssaallvvaarreeaa ssaauu îînnccăărrccaarreeaa uunnuuii jjoocc

Odată ce toate celelalte detalii importante au fost puse la punct şi jocul este

funcţional se va încerca crearea unei opţiuni de meniu de oprire temporară a jocului, fără a se ieşi din joc, ca mai apoi jocul să poată fi reluat. De aceea tot ce este în funcţia timerului va fi executat doar dacă pauza nu este activă.

Pentru a salva un joc vom lua în considerare toate variabilele de stare pe baza cărora putem reconstitui jocul de unde acesta a fost salvat, fără totuşi a crea un fişier de salvare supradimensionat. La încărcarea acestuia doar setăm variabilele de stare la cele specificate în fişierul de salvare şi resetăm engine-ul jocului.

Page 181: Programare Orientata Pe Obiecte

180

Programare C++ pe obiecte 5.7 Laborator de proiect 7.

55..77..11 TTeessttaarreeaa pprrooggrraammeelloorr.. BBeettaa--tteessttiinngg.. Jocul pare a fi pus la punct în momentul de faţă, şi totuşi treaba nu este terminată

trebuie să îl testăm, ceea ce în engleză se cheamă beta - testing. Înainte de a supune jocul acestei testări nu aveam decât o variantă beta.

Vom vedea dacă putem face faţă standardelor cerute de jocul pe care îl jucăm, vom observă că sunt momente în care jocul deşi funcţionează corect este foarte greu de parcurs în lipsa unui antrenament susţinut. Totuşi proiectul nu-şi propune ca cei care l-au creat să fie şi –buni - jucători ai jocului creat, de aceea vom recurge la trişare prin introducere în codul jocului de bonusuri, vieţi infinite, invulnerabilitate, încetinirea mişcărilor mingii, sau monştrilor toate disponibile la tastarea unui cuvânt cheie.

Astfel putem parcurge cap – coadă toate nivelele (nivelul) jocului depistând defectele (bug-urile) care s-au strecurat în programarea jocului. Niciodată nu este prea târziu să se corecteze un defect, dar se urmăreşte ca prin beta-testing acestea să fie cât mai reduse.

Cea mai frecventă eroare fatală este că s-a uitat să se aloce memorie pentru un

pointer, sau acestui pointer nu i s-a specificat o zonă de memorie alocată. O altă eroare este că în explorarea unui vector indexul s-a deplasat înafara zonei alocate acestuia. Altă eroare ar fi că s-a uitat un fişier deschis, sau că s-a apelat un fişier ce nu a fost deschis. Pot apare şi erori specifice claselor cum ar fi accesul la membrii unei clase, dar aceste sunt de obicei semnalate de compilator. Alte erori de funcţionare ar fi că varibilele nu au fost iniţializate corect, sau au fost dar doar la o singură apelare a secvenţei, la următoare fiind luate valorile precedente. Lista de erori ar putea continua, şi aici vorbesc de erori care lasă programul să meargă până în momentul în care secţiunea incorectă sau corectă este apelată cu nişte parametri corecţi sau incorecţi şi se iese forţat din joc sau mai mult se produce o cădere a sistemului şi este nevoie resetarea calculatorului.

55..77..22 DDiirreeccţţiiii ddee ddeezzvvoollttaarree

Dacă un joc are succes la public sau pentru a avea mai mult succes la public

atunci se doreşte îmbunătăţirea lui, nu atât a engine-ului cât a elementelor de joc. Se caută o diversificare a elementelor de joc,o îmbogăţire a scenariilor, o grafică mai bună, un IA mai performant şi mai eficient. De obicei se pune mai puţin accent pe grafică sau pe sunet şi mai mult pe ideile vehiculate şi atmosfera creată de joc, dacă acestea sunt bune atunci se merită să se îmbunătăţească grafică, dacă din start sunt perimate sau nefuncţionale cel puţin nu se lucrează degeaba la creaţia artistică. Uneori se practică şi o grafică mai bună pentru jocuri „bătrâne” ceea ce uneori are succes. Cele mai multe jocuri de astăzi permit jocul în echipă, ceea ce a început să fie o trăsătură de bază pentru jocurile care se respectă.

Pentru jocurile din cadrul proiectului se pot sugera multe direcţii de dezvoltare

dacă s-a luat în serios studierea ludografiei pentru acel joc. O serie de idei ar fi:

Page 182: Programare Orientata Pe Obiecte

181

Pentru jocul de tenis – cărămizile să fie de mai multe tipuri, să apară bonusuri la spargerea unei cărămizi, un bonus să permită mişcare mai repede a paletei,un bonus să permită ca paleta să poată „împuşca” cărămizile,altul se permită generarea mai multor mingi, altul să genereze mingi care sa aibă alte tipuri de efecte distructive, în funcţie de tipul mingii cărămizile să fie distruse într-un anumit fel.

Pentru jocul de tetris – se poate îmbunătăţi aspectul grafic al blocurilor de la simple conglomerate rectangulare la diferite aspecte stilizate, dispariţia unui rând complet de cărămizi să fie însoţită de un efect grafic special.

Pentru jocul de Dyna – să fie introduse bonusurile, să fie mai multe tipuri bombe, unele bonusuri să pună la dispoziţie şi pistoale omuleţului cu care doar să imobilizeze, să rănească monştri, sau să-i distrugă, să se creeze specii de monştri mai inteligenţi.

Pentru Shooter – să se introducă nave inamice care să atace nava jucătorului, să se introducă bonusurile, pentru îmbunătăţirea armelor navei, a sistemelor de propulsie, a scutului.

55..77..33 CCoonncclluuzziiii.. UUttiilliittaatteeaa pprrooiieeccttuulluuii..

S-a dorit ca crearea unei aplicaţii de tip joc să facă mai atractiv proiectul. Deşi

proiectul a fost un joc, creare jocului nu a fost o joacă. S-a pus accentul pe lucrul în echipă, dar şi pe studiul individual. Cu ocazia aceasta s-a creat un stil de lucru, un mod de abordare a unui limbaj de programare din perspectiva unei finalităţi practice. Lucrările de laborator au urmărit să ghideze studentul nu să-i ofere o soluţie completă sau definitivă. Studentului i s-au sugerat anumite soluţii i s-au trasat anumite etape obligatorii pe care trebuie să le parcurgă ca în final să obţină o aplicaţie funcţională dar şi cu aspect comercial şi chiar profesional. S-a dorit ca aceasta să fie un punct de pornire pentru viitori programatori profesionişti sau doar o îmbunătăţire a stilului de programare sau un simplu exerciţiu pentru cine stăpâneşte arta programării unei aplicaţii serioase.

Page 183: Programare Orientata Pe Obiecte

182

6 BIBLIOGRAFIE

1. Dr.Kris Jamsa & Lars Klander, Totul despre C şi C++, Teora, Bucureşti, 2003;

2. Herbert Schildt,C++ manual complet , Herbert Schildt, Teora, 2002; 3. Ivor Horton,Begining Visual C++ 2005, Wiley Publishing,Inc. , 2006 4. Jon Bates & Tim Tompkins, Utilizare Visual C++ 6 , Teora, Bucureşti,

2001; 5. Mircea Dorin Popovici & Mircea Ioan Popovici, C++ Tehnologia

orientată spre obiecte – Aplicaţii, Teora, Bucureşti, 2002; 6. Stephen Prata, Manual de programare în C++, Teora, Bucureşti, 2001; 7. www.cplusplus.com 8. http://msdn2.microsoft.com/en-us/visualc/default.aspx 9. http://gcc.gnu.org/ 10. www.parashift.com/c++-faq-lite 11. www.faqs.org/faqs/C++-faq/ 12 . www.codeguru.com/forum/ 13. www.freeprogrammingresources.com/cforum.html 14. www.codeproject.com/cpp/cppforumfaq.asp

Page 184: Programare Orientata Pe Obiecte

183

1 CONCEPTE GENERALE 3

1.1 Introducere...................................................................................... 3 1.2 Caracteristicile unui limbaj orientat pe obiect.................................. 7

2 PROGRAMARE ORIENTATĂ PE OBIECTE ÎN C++ 8 2.1 Clase şi obiecte............................................................................... 8 2.2 Clase derivate ............................................................................... 11 2.3 Utilizarea variabilelor globale sau a funcţiilor globale ................... 12 în definirea funcţiilor membre a unor clase......................................... 12 2.4 Funcţii inline .................................................................................. 14 2.5 Functii de tipul prieten friend........................................................ 15 2.6 Constructori şi destructori ............................................................. 16 2.7 Referinţe ....................................................................................... 20 2.8 Membrii statici ai unei clase .......................................................... 21 2.9 Sistemul de I/E din C++ ................................................................ 21 2.10 Utilizarea funcţiilor width(), precision() şi fill() .............................. 25 2.11 Supraîncărcarea funcţiilor şi operatorilor .................................... 26 2.12 Funcţie operator .......................................................................... 26 2.13 Supraîncărcarea operatorilor << şi >> ........................................ 31 2.14 Definirea de manipulatori personalizaţi ....................................... 33 2.15 Lucrul cu fişierele în C++ ............................................................ 35 2.16 Prelucrare binară a fişierelor ....................................................... 36 2.17 Lucrul aleatoriu cu fişierele ......................................................... 37 2.18 Cuvântul cheie this...................................................................... 38 2.19 Prevenirea redeclarării claselor................................................... 39 2.20 Tehnici de creare şi iniţializare a obiectelor ............................... 40 2.21 Elemente despre preprocesare................................................... 41 2.22 Directive de compilare condiţionată ............................................ 42 2.23 Prevenirea redeclarării claselor................................................... 43

3 PROGRAMAREA ÎN VISUAL C++ 44 3.1 Crearea unui proiect ..................................................................... 44 3.2 Fereastra interfeţei cu utilizatorul.................................................. 44 3.3 Efectuarea compilării şi a editării de legături ................................ 48 3.4 Modificarea interfeţei aplicaţiei...................................................... 50 3.5 Asocierea de cod cu interfaţa ....................................................... 54 3.6 Salvarea şi închiderea proiectului ................................................. 58 3.7 Utilizarea mediului Deweloper Studio ........................................... 59 3.8 Personalizarea mediului Developer Studio ................................... 59 3.9 Deschiderea unui proiect existent................................................. 59 3.10 Fereastra spaţiului de lucru ai proiectului.................................... 61 3.11 Lucrul cu reprezentarea claselor................................................. 61 3.12 Resurse Visual C++ .................................................................... 68 3.13 Tipuri de resurse ......................................................................... 69 3.14 Utilizarea controalelor ................................................................. 73

Page 185: Programare Orientata Pe Obiecte

184

3.15 Lucrul cu imagini în Visual C++................................................... 77 3.16 Lucrul cu fişiere în Visual C++ .................................................... 81 3.17 Elemente de grafică în Visual C++.............................................. 86

4 LUCRĂRI DE LABORATOR 101 4.1 Lucrarea de laborator 1............................................................... 101 4.2 Lucrarea de laborator 2............................................................... 108 4.3 Lucrarea de laborator 3............................................................... 114 4.4 Lucrarea de laborator 4............................................................... 121 4.5 Lucrarea de laborator 5............................................................... 133 4.6 Lucrarea de laborator 6............................................................... 139 4.7 Lucrarea de laborator 7............................................................... 147

5 ÎNDRUMAR DE PROIECT 155 5.1 Laborator de proiect 1................................................................. 155 5.2 Laborator de proiect 2................................................................. 157 5.3 Laborator de proiect 3................................................................. 161 5.4 Laborator de proiect 4................................................................. 165 5.6 Laborator de proiect 6................................................................. 177 5.7 Laborator de proiect 7................................................................. 180

6 BIBLIOGRAFIE 182