Programare Visual Studio .NET Versiunea 2 Nov 2011

101
Programare Microsoft Visual Studio .NET Note de curs Version no.: 2 Page 1 of 101

description

ok

Transcript of Programare Visual Studio .NET Versiunea 2 Nov 2011

Page 1: Programare Visual Studio .NET Versiunea 2 Nov 2011

Programare Microsoft Visual Studio .NET

Note de curs

Version no.: 2 Page 1 of 82

Page 2: Programare Visual Studio .NET Versiunea 2 Nov 2011

Cuprins1 INTRODUCERE........................................................................................................................................................4

2 PROGRAMARE ORIENTATĂ OBIECT (POO)................................................................................................7

2.1 INTRODUCERE.....................................................................................................................................................72.1.1 Ce este POO?.............................................................................................................................................72.1.2 Ce este un obiect?.......................................................................................................................................72.1.3 Ce este o clasă?..........................................................................................................................................7

2.2 CREAREA APLICAŢIILOR POO ÎN MEDIUL DE DEZVOLTARE MICROSOFT VISUAL STUDIO.................................92.2.1 Crearea proietelor de tip "Console Application".......................................................................................92.2.2 Secţiunile unui proiect creat în VS.............................................................................................................92.2.3 Adăugarea de noi clase în VS...................................................................................................................102.2.4 Lansarea în execuţie a proiectelor...........................................................................................................11

2.3 ARHITECTURA CLASELOR ÎN POO....................................................................................................................122.3.1 Elementele componente ale unei clase.....................................................................................................12

2.3.1.1 Declararea variabilelor în POO..............................................................................................................................132.3.2 Analiza clasei "Person"............................................................................................................................132.3.3 Instantierea Claselor................................................................................................................................142.3.4 Constructori. Overloading.......................................................................................................................142.3.5 Domeniul de vizibilitate pentru componentele unei clase........................................................................162.3.6 Domenii de vizibilitate pentru o clasă......................................................................................................17

2.4 CONCEPTELE PROGRAMĂRII ORIENTATE OBIECT.............................................................................................182.4.1 Abstractizare.............................................................................................................................................18

2.4.1.1 Referinţa obiectelor................................................................................................................................................182.4.2 Incapsulare...............................................................................................................................................19

2.4.2.1 Conceptul de încapsulare........................................................................................................................................192.4.2.2 Interfaţa clasei Person.............................................................................................................................................19

2.4.3 Moştenirea................................................................................................................................................212.4.3.1 Conceptul de moştenire..........................................................................................................................................212.4.3.2 Suprascriere (override)...........................................................................................................................................232.4.3.3 Identificatorii “this” şi "base"................................................................................................................................242.4.3.4 Metode şi variabile statice......................................................................................................................................262.4.3.5 Clase abstracte........................................................................................................................................................28

2.4.4 Polimorfismul...........................................................................................................................................282.5 ASPECTE ALE POO ÎN MEDIUL DE DEZVOLTARE MICROSOFT VISUAL STUDIO................................................30

2.5.1 Garbage collection...................................................................................................................................302.5.1.1 Eliberarea memoriei în mod automat.....................................................................................................................302.5.1.2 Eliberarea memoriei la cerere.................................................................................................................................31

2.5.2 Excepţii şi tratarea excepţiilor.................................................................................................................32

3 DEZVOLTAREA APLICAŢIILOR DE TIP GUI IN VS.NET.........................................................................35

3.1 ETAPELE DEZVOLTĂRII UNEI APLICAŢII WINDOWS ÎN VS.NET.......................................................................353.2 PRIMA APLICATIE WINDOWS: "HELLO WORLD"...............................................................................................36

3.2.1 Funcţia InitializeComponent....................................................................................................................403.2.2 Interceptarea evenimentelor.....................................................................................................................40

3.2.2.1 Evenimente şi delegaţi. Soluţii cu proiecte multiple..............................................................................................423.2.2.2 Handlere multiple...................................................................................................................................................45

3.2.3 Concluzii...................................................................................................................................................463.3 UTILIZAREA CONTROALELOR STANDARD ÎN VS.NET.....................................................................................48

3.3.1 Controlul TextBox....................................................................................................................................483.3.1.1 Validarea informaţiilor din TextBox......................................................................................................................50

3.3.2 Controlul ToolTip.....................................................................................................................................503.3.3 Controlul CheckBox (caseta de validare)................................................................................................513.3.4 Controlul RadioButton (buton radio).......................................................................................................513.3.5 Controlul ComboBox (listă ascunsă).......................................................................................................53

Version no.: 2 Page 2 of 82

Page 3: Programare Visual Studio .NET Versiunea 2 Nov 2011

4 SALVAREA DATELOR DE LUCRU..................................................................................................................55

4.1 SALVAREA DATELOR ÎN FIŞIER..........................................................................................................................554.1.1 Citirea datelor din fisier...........................................................................................................................56

4.2 SERIALIZARE.....................................................................................................................................................574.2.1 Salvarea informaţiilor în clasa Student....................................................................................................574.2.2 Serializarea clasei Student.......................................................................................................................57

4.3 SALVAREA INFORMAŢIILOR ÎN BAZE DE DATE..................................................................................................624.3.1 Ce este o bază de date..............................................................................................................................624.3.2 Instalarea bazei de date............................................................................................................................634.3.3 Crearea tabelelor.....................................................................................................................................634.3.4 Salvarea obiectului "Student" în baza de date.........................................................................................66

4.3.4.1 Tehnologia ADO.NET............................................................................................................................................664.3.4.2 Stabilirea conexiunii la baza de date......................................................................................................................674.3.4.3 Funcţia de salvare a obiectului "Student" în baza de date......................................................................................68

4.3.5 Afisarea dinamică a mesajelor. Controalele StatusStrip şi Timer...........................................................70

5 APLICAŢIA "E-STUDENT"...............................................................................................................................72

5.1 COLECŢII DE OBIECTE.......................................................................................................................................725.2 CITIREA LISTELOR DE STUDENŢI DIN BAZA DE DATE........................................................................................735.3 FORMA "E-STUDENT".......................................................................................................................................74

5.3.1 Afişarea anilor universitari......................................................................................................................745.3.2 Afişarea grupelor......................................................................................................................................755.3.3 Popularea automată a listelor de tip ComboBox.....................................................................................775.3.4 Afişarea studenţilor. Controlul DatGridView..........................................................................................775.3.5 Interceptarea evenimentelor date de grid................................................................................................805.3.6 Inscrierea studenţilor în grupe.................................................................................................................835.3.7 Conectarea aplicaţiei la diverse tipuri de baze de date...........................................................................85

6 Bibliografie...............................................................................................................................................................87

Version no.: 2 Page 3 of 82

Page 4: Programare Visual Studio .NET Versiunea 2 Nov 2011

1 Introducere

“Hello world” ... probabil este cel mai des utilizat exemplu când se începe un nou curs de programare. Acest “Hello world” a fost introdus de Brian Kernighan în 1974 când a scris prima carte de programare în limbajul C. Cine nu cunoaşte (sau mai bine zis, care programator nu cunoaşte) celebrul exemplu de la care a pornit era limbajelor de programare de nivel înalt?

int main() { printf("hello, world"); return 0; }

De fapt acest program demonstra ce simplu este în noul limbaj C să afişezi un text pe ecran. Era o mare realizare la vremea aceea, să scrii un text pe ecran într-un mod atât de simplu. De ce era o mare realizare?

Pentru că, înainte de a apărea acest C, lumea programa în limbaj de asamblare. Limbajul de asamblare este limbajul pe care-l înţelege microprocesorul. Si nu este un limbaj foarte evoluat, microprocesorul este un circuit integrat care înţelege şi poate executa un set limitat de instrucţiuni. Tot ce ştie să facă microprocesorul se restrânge la o serie de operaţii de adunare, scădere (bine, acum ştie să facă şi înmulţiri în virgulă mobilă pentru că are procesorul matematic încorporat), încărcare de regiştri, citire/scriere din memorie. De exemplu, pentru a face o adunare banală de 2+3, lucrând direct cu microprocesorul, ar trebui să facem următorul progrămel:

LD A,2 ; incarca in registrul A numarul 2ADD A,3 ; aduna la valoarea din registrul A numarul 3, rezultatul se salveaza tot in ALD (HL),A ; salveaza in memorie la adresa data de registrul HL continutul lui A

Paradoxal la prima vedere, calculatorul care ştie să rezolve atât de multe probleme şi atât de complicate, se bazează pe un circuit ce nu cunoaşte decât operaţii aritmetice. Si totuşi, oricât de complicată ar fi o funcţie, aceasta se poate descompune într-o serie de operaţii aritmetice (serie Taylor) care să poată fi executate de microprocesor. De exemplu, funcţia sin(x) pentru un x dat se poate calcula prin seria Taylor echivalentă:

Unde x3 se calculează prin înmulţirea de 3 ori a lui x cu el însuşi, şi tot aşa.

Concluzia este că, orice problemă trebuie împărţită într-un șir de operaţii mici ce pot fi executate de microprocesor. Această operaţie de algoritmizare a unei probleme, împreună cu scrierea codului echivalent care să traducă taskurile rezultate într-un șir de instrucţiuni ce pot fi executate de microprocesor, se numește programare.

Evident, nu trebuie mers cu despicatul problemei până la nivelul de jos al microprocesorului, pentru că în timp au apărut compilatoarele, sau mai bine zis limbajele de programare. Este clar că pentru un programator este foarte dificil să reducă problema doar la un șir de operaţii aritmetice, cum este la fel de evident că microprocesorul nu poate mai mult. De aceea, s-a creat această interfaţă între programator și microprocesor care să traducă operaţii de nivel înalt (cu care lucrează programatorul) într-un șir de operaţii mici la nivelul codului mașină. Folosind aceste limbaje de programare, programatorul nu mai este nevoit să scrie “LD A, 2 ....”, ci scrie direct “x=2+3”, mult mai simplu și mai citeţ.

In plus, aceste limbaje vin cu o serie complexă de biblioteci ce conţin funcţii gata facute care ne ajută să rezolvăm problema. De ce să mă chinui eu să descompun funcţia sin(x) în serie Taylor, când pot apela pur și simplu funcţia sin(x) din biblioteca matematică. Ar fi foarte dificil pentru mine să scriu un program care să traseze o linie pe ecran, sau să scriu un text pe ecran. Ar trebui să știu o mulţime de detalii legate de monitor, de adrese de memorie, modul de transmisie a informaţiei la monitor, etc. Ca să trasez o linie, mi-ar trebui o pagina de cod și zile bune de cercetare. Este mult mai simplu să apelez funcţia “lineto(x,y)” din biblioteca grafică, sau funcţia ”printf()” pentru a scrie un text pe ecran (mai retineţi exemplul “printf(“hello world”) ?).

Version no.: 2 Page 4 of 82

Page 5: Programare Visual Studio .NET Versiunea 2 Nov 2011

Aceste limbaje de programare au evoluat continuu, de la revoluţionarul la vremea respectivă, dar acum banalul C, până la sistemele “Visual” din zilele noastre în care programatorul scrie cod doar din operaţii de mouse.

Sunt o multitudine de limbaje de programare pe piaţă, fiecare având avantaje specifice pentru un anumit domeniu de utilizare.

La nivelul de jos se situează limbajele de asamblare. Instrucţiunile acestor limbaje sunt apropiate de modul de lucru al microprocesorului şi foarte departe de modul nostru de a gândi o rezolvare la o problemă dată. O adunare banală de forma "val_total = val + val_TVA" se traduce în asamblare într-un set destul de mare de instrucţiuni microprocesor mai puţin inteligibile. Chiar dacă la un moment dat limbajele de asamblare erau la modă (unii chiar aveau sisteme multitasking dezvoltate în asamblare), astăzi nu se mai justifică programarea în asamblare. Avantajele acestui sistem (viteza mare de execuţie şi consum redus de memorie) nu mai reprezintă un argument suficient de puternic în faţa dezavantajelor evidente în dezvoltarea aplicaţiilor.

Imediat peste limbajul de asamblare sunt limbajele de programare clasice: C standard, Basic, Pascal, Fortran, Cobol, etc. Au apărut din necesitatea unei interfeţe agreabile între programator şi microprocesor. Programează astăzi cineva în limbaje clasice? Da, şi încă foarte mult. Să nu uităm că majoritatea automatizărilor sunt realizate cu microcontrolere. Un microcontroler este un calculator în miniatură: într-un singur circuit integrat este inclus microprocesorul, ceva memorie (512 octeti RAM, 2 KO memorie FLASH, 1 KO EEPROM), mai sunt circuitele de intrare/ieşire, convertoare AD/DA, circuite de numărare/temporizare. În viaţa de zi cu zi suntem înconjuraţi de aparate cu microcontroler: televizorul, maşina de spălat automată, cuptorul cu microunde, autovehiculul etc. Toate aceste microcontrolere au în dotare un compilator de C în care programatorul poate să dezvolte propriul algoritm de automatizare.

Totuşi, când este vorba de interfaţă grafică cu utilizatorul, limbajele clasice nu mai fac faţă. Trebuie să trecem la sisteme vizuale de programare: Visual C++, Visual Basic, Java, Lab View. Majoritatea aplicaţiilor de pe calculatorul dumneavoastră sunt construite într-un limbaj vizual. Ne-am obişnuit atât de mult cu butoane, meniuri, ferestre, mouse, încât nu mai concepem să utilizăm programe care să nu prezinte asemenea facilităţi.

Programarea într-un mediu vizual a schimbat în mod radical tehnica dezvoltării programelor. S-a trecut la programarea bazată pe evenimente. Programul nostru este format dintr-un număr de obiecte (butoane, text-box-uri, datagrid-uri, liste ... ) , majoritatea luate din platforma de dezvoltare, altele construite de noi, obiecte ce răspund la diverse evenimente transmise de sistemul de operare. Astfel, la o simplă mişcare a mouse-ului pe ecran, sistemul de operare trimite zeci de mesaje MOUSE-MOVE obiectului deasupra căruia se află în acel moment mouse-ul. Se face click pe butonul mouse-ului, obiectul respectiv primeste mesajul MOUSE-CLICK. Programul nostru nu face altceva decât să aştepte mesajele şi le tratează pe acelea care ne interesează. E clar ca dacă vine un mesaj MOUSE-CLICK la un buton pe care noi am scris SAVE, trebuie să scriem ceva cod care să rezolve acel eveniment şi anume, să salvăm ceea ce este de salvat în acel context. Dar dacă vin mesaje de tipul WM_SIZE (care ne atenţionează că utilizatorul tocmai modifică mărimea obiectului), atunci lăsăm mesajul să meargă mai departe, nu-l interceptam. Mesajele neinterceptate de aplicaţia noastră sunt preluate de Windows care rezolvă problema şi redesenează obiectul la mărimea corespunzătoare.

Deci ce sistem de programare folosesc? Ultima tehnologie de programare Visual Studio .NET dezvoltată de Microsoft reprezintă în mod cert alegerea potrivită pentru majoritatea programelor de aplicaţii. De mult timp se aşteaptă o platformă de programare în care să se poată dezvolta module în limbaje diferite, să ruleze pe orice sistem, să nu aibă probleme de compatibilitate cu diversele versiuni de DLL-uri şi să aibă un set complet de controale Windows. Visual Studio .NET vine în întâmpinarea acestor cerinţe, anunţându-se ca fiind sistemul de programare ce va fi utilizat în următorii ani.

Mai contează C sau Basic? Sub platforma .NET nu prea mai contează. Inainte, programatorii erau de acord că, dacă trebuie construit un proiect mare, acesta nu poate fi făcut decât în C++. Pentru proiecte mai mici se utiliza Visual Basic care era mai uşor de utilizat şi avea o interfaţă mult mai agreabilă. Acum, cele două limbaje au ajuns la acelaşi nivel, Basic-ul integrând toate conceptele care erau apanajul C-ului (moştenire, multithreading), iar C-ul căpătând acea interfaţă agreabilă cu care ne-a obişnuit Basic-ul. Deci, alegerea unui limbaj sau altul rămâne doar o chestiune de gust.

Version no.: 2 Page 5 of 82

Page 6: Programare Visual Studio .NET Versiunea 2 Nov 2011

Si totusi, Microsoft a scos limbajul C#, un limbaj conceput de la inceput să funcţioneze pe obiecte, deci el implementează în mod nativ toate conceptele programării pe obiect. Din acest motiv se consideră ca C# este alegerea potrivită pentru demararea unei aplicaţii software complexe. Iar acest limbaj face și obiectul cursului de faţă.

Acest curs se adresează începătorilor în domeniul programării orientată obiect. Cititorul trebuie doar să aibă câteva noţiuni simple de limbaj C (sau orice alt limbaj clasic) pentru a înţelege conceptele definite aici.

Sunt prezentate la început câteva noţiuni de bază ale programării pe obiecte, noţiuni pe care se bazează orice program scris în această tehnică.

Se trece apoi la programarea aplicaţiilor de tip "user interface" cu toate aspectele specifice: controale vizuale, proprietăţi specifice, evenimente şi tratarea evenimentelor.

O bună parte din curs tratează modul de lucru cu bazele de date sau alte tehnici de salvare a datelor. Aceasta deoarece, orice program mai serios în ziua de astăzi are în spate o bază de date. Programele în general lucrează cu date şi cel mai sigur mod de a menţine coerenţa datelor îl reprezintă baza de date.

Toate noţiunile sunt exemplificate prin programe concrete care se pun cap la cap în timpul derulării cursului astfel încât, la sfârşit să avem o aplicaţie funcţională de o complexitate medie. In acest mod, cursul devine mai puţin plicticos, studentul având tot timpul posibilitatea să verifice şi să adauge noi funcţionalităţi care i se par interesante.

Cursul este de tip introductiv, având scopul deşteptării unui anumit interes faţă de acest domeniu al programării. Programarea nu este o meserie grea (bine, poate fi şi grea în anumite situaţii), dar poate fi extrem de interesantă şi atractivă după ce ai trecut de primele obstacole ale înţelegerii fenomenului. Frumuseţea derivă din faptul că tot ce ai nevoie ca să construieşti o aplicaţie, este un calculator şi în orice moment poţi vedea rezultatele muncii tale în mod direct. Acest fapt îţi dă încredere şi curaj să mergi mai departe. Nu ai nevoie de maşini unelte, materie primă, sau alte asemenea utilităţi, ci doar de imaginaţie, optimism şi chef de lucru. In rest, prin intermediul internetului, toată informaţia necesară iţi stă la dispoziţie.Nu mai amintim faptul că este o meserie curată, unde se câştigă destul de bine şi cu care te poţi angaja oriunde în lume, fără a se ţine seama de diferenţa tehnologică între ţara unde ai învăţat şi ţara unde lucrezi.

Version no.: 2 Page 6 of 82

Page 7: Programare Visual Studio .NET Versiunea 2 Nov 2011

2 Programare Orientată Obiect (POO)

2.1 Introducere

2.1.1 Ce este POO?

POO (OOP Object Oriented Programming) reprezintă o metodă, un concept de implementare a programelor software. Dacă în vechile limbaje de programare C, Pascal, structura unui program era compusă dintr-o succesiune de funcţii ce se apelau între ele, un program construit în filozofia POO este format dintr-o colecţie de obiecte de sine stătătoare ce interacţioneză între ele pentru îndeplinirea scopului final.Programarea orientată pe obiecte este metoda utilizată aproape exclusiv în momentul de faţă în toate domeniile tehnologiilor informaţionale. Acest succes se datorează modului în care clasele şi obiectele definite într-un program orientat obiect emulează lumea reală. Astfel, orice obiect din lumea reală (persoană, maşină, televizor, etc…) se caracterizează prin două concepte:

proprietăţi: ( culoare, marcă, tensiune de alimentare, salariu …) ce definesc componenta statică a obiectului;

funcţii (metode): (schimbă volum, deplasează, ...) prin care se modifică comportamentul obiectului.

In POO o colecţie de obiecte de acelaşi tip formează o clasă de obiecte. Clasele formează o ierarhie, pornind de la o clasă de bază ce reprezintă o abstractizare simplă a realităţii, la care, prin folosirea conceptului de moştenire se adaugă pe rând noi detalii specifice, ajungând la clase mult mai complicate, ce definesc într-un mod fidel obiectele reale. Acest mecanism de moştenire a claselor prin formarea unor clase derivate dintr-o clasă de bază a permis o creştere considerabilă a productivităţii în programare, deoarece toată munca inclusă în clasa de bază se refoloseşte în clasa derivată.

De exemplu, pentru adăugarea unui buton în aplicaţia noastră, noi trebuie să instanţiem un obiect din clasa "Button". Această clasă include proprietăţi şi metode specifice unui buton:

Proprietăţi: culoare, lungime, lăţime, poziţie, fontul de scriere, etc. Funcţii: Focus, GetHashCode, ToString, Click

Putem observa că multe din aceste metode aparţin oricărei ferestre (Window) de uz general. Un Buton este până la urmă o fereastră particularizată pe anumite funcţii. De aceea este mult mai uşor să folosim clasa Window şi să-i adăugam căteva evenimente şi funcţii specifice unui buton (funcţia ButtonClick de exemplu), decât să construim clasa buton de la zero. Toate proprietăţile unei ferestre (culoare, poziţie,..) se păstrează în noua clasa "Button" prin moştenire, deci nu mai trebuie create de la zero.La fel se moştenesc din clasa Window şi alte controale de tip GU (Graphic Interface): TextBox, ComboBox, MessageBox, etc.

2.1.2 Ce este un obiect?

Un obiect poate fi considerat ca ceva real, concret, ce poate realiza anumite funcţii. Multitudinea de activităţi ce pot fi executate de către un obiect definesc funcţionalitatea obiectului (obiectul Button execută o funcţie când se face click pe el, obiectul de tip Window îşi modifică dimensiunile când utilizatorul "trage" cu mouse-ul de marginile acesteia).

2.1.3 Ce este o clasă?

In termenii POO, un obiect este o instanţă a unei clase. Clasa reprezintă o definiţie a obiectului, un plan, un tipar ce descrie funcţionalitatea obiectului. In cele ce urmează ne propunem să definim o clasă simplă numita "Person" care să includă câteva din atributele caracteristice unei persoane din lumea reală: nume, prenume, data naşterii. Pe baza acestei definiţii vom deriva alte două clase care să conţină noi atribute, dar de data aceasta specifice unui student şi unui profesor. Studentul şi profesorul reprezintă persoane in primul rând, deci sunt caracterizate de nume, prenume, etc, dar au şi atribute proprii domeniilor lor de activitate: bursier, şef de catedră, etc.

Version no.: 2 Page 7 of 82

Page 8: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.2 Crearea aplicaţiilor POO în mediul de dezvoltare Microsoft Visual Studio

Pentru exemplificare şi implementarea conceptelor legate de POO vom folosi mediul de dezvoltare Microsoft Visual Studio 2008 (VS).

2.2.1 Crearea proietelor de tip "Console Application"

Primul pas ce trebuie făcut în realizarea unui program este să se creeze o nouă soluţie in VS, iar în cadrul acestei soluţii să se dezvolte diverse proiecte care puse cap la cap să rezolve taskul final (prin soluţie înţelegem o colecţie de proiecte ce colaborează pentru rezolvarea unui task).Se deschide VS şi se alege numele soluţiei si al primului proiect pe care dorim să-l implementăm:

Setăm pentru soluţie numele "OOP Course", iar primul proiect va fi "OOP Fundamentals". Foarte important, alegem tiparul (template) acestui proiect de tip "Console Application". Este un tip simplu, ce permite verificarea rapidă a conceptelor de programare pe obiect.

2.2.2 Secţiunile unui proiect creat în VS

După ce s-a selectat tipul de proiect, VS creează în mod automat structura soluţiei şi fişierele de început ale proiectului:

Version no.: 2 Page 8 of 82

Page 9: Programare Visual Studio .NET Versiunea 2 Nov 2011

Observăm proiectul OOP Fundamentals ce include cateva secţiuni întâlnite în toate aplicaţiile:o Secţiunea Properties unde se află fişierul AssemblyInfo.cs. Acest fişier conţine

informaţii despre proiect: versiune, titlu, copyright, etc.o Secţiunea References: sunt incluse librăriile utilizate in cadrul acestui proiect. Aici

putem adauga atat librării din biblioteca VS.NET, cat şi alte surse de cod: proiecte din cadrul aceleeaşi soluţii, sau fişiere DDL. Orice sursa externă de cod trebuie mai intâi adăugată in secţiunea References ca să poată fi vizibilă şi utilizată in cod.

o Clasa "Program": este clasa implicită a proiectului, punctul de intrare în program. Ca şi în limbajul C clasic, aici se gaseşte funcţia Main() de unde va porni rularea aplicaţiei. In această funcţie programatorul trebuie să adauge cod care să rezolve taskul proiectului.

o ClassDiagram si Person sunt fişiere adaugate de noi despre care vom vorbi în continuare.

2.2.3 Adăugarea de noi clase în VS

In continuare vom adăuga o nouă clasă în proiect: click dreapta pe proiectul "OOP Fundamentals" şi se alege opţiunea Add Class, numele noii clase va fi "Person":

Codul clasei "Person" este dat în continuare:

Version no.: 2 Page 9 of 82

Page 10: Programare Visual Studio .NET Versiunea 2 Nov 2011

Componenţa clasei Person se poate vedea mai bine dacă se construieşte diagrama acesteia. Click dreapta pe clasă şi se alege opţiunea "View Class Diagram":

2.2.4 Lansarea în execuţie a proiectelor

Fiecare nou proiect creat în VS are o clasă implicită numita "Program" ce conţine funcţia Main(), punctul de lansare a programului.Adaugăm în această funcţie câteva linii de cod care să creeze un obiect din clasa Person, definim numele, prenumele şi data naşterii pentru obiectul creat şi apoi se afişează aceste informaţii la consolă:

Version no.: 2 Page 10 of 82

Page 11: Programare Visual Studio .NET Versiunea 2 Nov 2011

După completarea clasei Program se apasă tasta F5 ce realizează următoarele taskuri în VS:- salvează toate fişierele din proiect ce au fost modificate- compilează, link-editează şi construieşte fişierul final "OOP Fundamentals.exe"- lansează în execuţie fişierul "OOP Fundamentals.exe" utilizând modul de depanare a programului

(Debug). Acest mod de execuţie permite inserarea de puncte de întrerupere în program (Breakpoints) unde utilizatorul poate accesa variabilele interne şi analizează modul de execuţie.Programul se poate opri oricând din execuţie prin apăsarea tastei SHIFT+F5.

2.3 Arhitectura claselor în POO

2.3.1 Elementele componente ale unei clase

Clasa Person creată mai sus este o clasă foarte simplă, dar conţine cele trei componente de bază a oricărei clase:

Câmpuri (Fields): reprezintă variabile interne ale clasei în care sunt stocate informaţiile de lucru curente. Aceste variabile pot avea diverse structuri, de la cele predefinite (int, long, double), până la structuri complexe definite prin intermediul altor clase.

Proprietăţi (properties): definesc interfaţa statică a clasei: seteaza sau furnizează valorile variabilelor interne. Proprietăţile au două secţiuni ce lucrează împreună: get şi set. Prima furnizează valoarea catre exterior, iar prin set se modifică din exterior valoarea variabilei interne. Nu-i o regulă obligatorie să existe ambele componente, pot exista cazuri ca o proprietate să fie "read only" deci permite doar citirea variabilei.

In marea majoritate a cazurilor, o proprietate este doar o reflexie a variabilei interne in exterior. Nu se face nici o procesare a informaţiei in momentul trecerii valorii din exterior in interior sau invers.De exemplu, în clasa Person există proprietatea "BirthDate" ce nu face altceva decât să facă accesibilă valoarea _birthDate în exterior:

public DateTime BirthDate { get { return _birthDate; } set { _birthDate = value; } }De aceea, se pot defini proprietăţi automate, la care VS creeză automat un câmp în spate care să păstreze valoarea setată prin proprietate:

public string Name { get; set; }

Version no.: 2 Page 11 of 82

Page 12: Programare Visual Studio .NET Versiunea 2 Nov 2011

Acest lucru ne salvează de a scrie multe linii de cod, iar funcţionalitatea este aceeaşi.

Observaţie: Proprietatea nu face altceva decât să facă publică valoarea variabilei interne. Acelaşi lucru se obţine dacă se declară acea variabilă drept "public", adică publică in exterior. Totuşi, este o regulă de bună programare in OOP care spune că nu-i bine să faci publice componentele interne ale clasei, ci întotdeauna acestea trebuie expuse printr-o proprietate care să controleze accesul la acea componentă.

Avantajul utilizării proprietăţilor se poate observa la proprietatea "BirthDateString":

public string BirthDateString { get { return _birthDate.ToString(dateTimeFormat, CultureInfo.InvariantCulture); } set { DateTime.TryParse(value, out _birthDate); } }

In acest caz, variabila internă nu este expusă direct la exterior, ci se face o conversie prealabilă din formatul DateTime în formatul şir de caractere.

Funcţii (Methods): reprezintă interfaţa dinamică a clasei, prin intermediul cărora clasa execută anumite taskuri atunci cand sunt accesate din exterior.

Nu toate funcţiile pot fi apelate din exterior, sunt funcţii de uz intern, vizibile doar în interiorul clasei. Vizibilitatea unei funcţii (sau în general, a oricărei componente) este dată de domeniul de vizibilitate setat pentru acea componentă.

2.3.1.1 Declararea variabilelor în POO

Ca şi în C clasic, orice variabilă trebuie declarată înainte de a fi utilizată. Avantajul în C# este că putem declara variabile în orice parte a programului, nu în mod neapărat la începutul lui. Declararea unei variabile presupune următoarele informaţii:

- domeniul de vizibilittate (public, private, etc.)- numele variabilei- tipul (double, int, string, DateTime, etc.)

2.3.2 Analiza clasei "Person"Clasa Person conţine două variabile interne:

_birthDate de tipul DateTime ce stochează data naşterii pentru persoana respectivă. Este o variabilă privată (vizibilă doar în aceasta clasă) ce este afişată în exterior prin două proprietăţi publice ( public string BirthDateString, public DateTime BirthDate) ;

private const string dateTimeFormat = @"dd-MMM-yyyy"; este un string folosit pentru formatarea datelor calendaristice în momentul afişării.

Observaţie: pentru stocarea unei date calendaristice s-a folosit structura DateTime definită în biblioteca "System". VS pune la dispoziţie un help util ce se afişează sub forma unui ToolTip în momentul când întârziem cu mouse-ul deasupra unui cuvânt cheie. Pentru DateTime helpul tipul acelui câmp si un mic text descriptiv:

Struct în C# are aceeaşi semnificaţie ca şi C clasic: o structură organizată de câmpuri si funcţii. Foarte asemănătoare cu o clasă, dar nu necesită instanţiere pentru a fi utilizată.

Proprietăţile clasei permit modificarea numelei, prenumelui şi a datei de naştere pentru acea persoană. Data de naştere poate fi modificată prin două proprietaţi, una primind ca parametru o structura DateTime ce

Version no.: 2 Page 12 of 82

Page 13: Programare Visual Studio .NET Versiunea 2 Nov 2011

înlocuieşte direct valoarea variabilei interne, a doua primeşte un string pe care-l converteşte la structura DateTime.

Observaţie: clasa "string" este o clasa nativa in C#, se utilizează pentru memorarea şirurilor de caractere. Este folosita in clasa “Person” pentru salvarea numelui si prenumelui.

Clasa Person are o singură metodă (funcţie) prin care se obţine un string format din cele două entităţi ale numelui concatenate.

2.3.3 Instantierea Claselor

Clasa “Person” reprezintă doar un şablon după care se creează obiecte (instanţe) de tip “Person”. Pentru a crea obiectul, trebuie folosită comanda “new”:

Person person1 = new Person();Prin instrucţiunea de mai sus s-a creat un obiect numit “person1” de tip “Person”. Comanda “new” alocă spaţiu de memorie pentru obiect şi-l depune la acea locaţie.

Se poate declara un obiect si fără crearea unei instanţe în memorie, dar pană nu se alocă spaţiu de memorie efectiv prin comanda new, acel obiect nu poate fi utilizat.

Person pers1; pers1 = new Person();Dacă se încearcă apelul unei metode din obiectul declarat , dar neinstanţiat,

Person pers2; pers2.Nume = "vasile..."; System.Console.WriteLine(pers2.GetNameSurname());C-ul dă eroare la compilare: “Use of unassigned local variable 'pers2' “.

In clasa Program se creează o instanţă (obiect) din clasa Person, se setează valorile pentru proprietăţile acesteia, apoi se afişează la consolă aceste informaţii.Chiar dacă acest program nu utilizează mai deloc conceptele POO, este un început pentru următoarele dezvoltări în arhitectura POO.

2.3.4 Constructori. Overloading

Constructorul este o funcţie specială care are acelaşi nume ca şi clasa de definiţie şi care este obligatorie în definiţia clasei. Chiar dacă programatorul nu scrie nici un constructor pentru clasa respectivă, automat VS-ul inserează un constructor null în clasă:

public Person(){}

Constructorul este prima funcţie ce se apelează automat după instanţierea obiectului (deja toţi membrii obiectului sunt alocaţi în memorie) şi se foloseşte pentru diverse setări iniţiale sau pentru preluarea informaţiilor furnizate de apelant la crearea obiectului prin comanda new.De exemplu, utilizatorul doreşte sa furnizeze direct numele persoanei in momentul creării obiectului printr-un apel de forma:

Person pers2 = new Person("Dosoftei", "Andrei");

Pentru ca acestă construcţie să meargă, trebuie creat un constructor care să preia cei doi parametri (nume şi prenume) şi să-i salveze în variabilele obiectului.

public Person(string name, string surname) { Name = name;

Version no.: 2 Page 13 of 82

Page 14: Programare Visual Studio .NET Versiunea 2 Nov 2011

Surname = surname; }

Atentie: daca se introduce un constructor cu un numar de parametri diferit de zero, trebuie sa se scrie si constructorul implicit (cel cu zero parametri: public Person(){ } ). In caz contrar apare eroarea: No overload for method 'Person' takes '0' arguments

La fel de bine, utilizatorul ar vrea să furnizeze pe lângă numele persoanei, şi data naşterii în momentul creării obiectului:

Person pers2 = new Person("Ionut","Popescu","02-Nov-1990"); DisplayPersonDetails(pers2);

Pentru aceasta, trebuie creat un nou constructor care să primească 3 parametri:

public Person(string name, string surname, string birthDate) { Name = name; Surname = surname; BirthDateString = birthDate; }

De observat că deşi cei doi constructori au acelaşi nume (nici nu au cum sa fie diferite deoarece constructorul moşteneşte numele clasei), la instanţierea obiectului nu apare nici o eroare, cu toate că în acel moment se apelează constructorul clasei. Care este constructorul apelat? Simplu, se apelează constructorul care se potriveşte cu numărul de parametri introduşi de apelant. Eroarea apare dacă se creează un obiect cu un numar de parametri ce nu se întâlneşte la nici un constructor.

Refolosirea aceluiaşi nume pentru mai multe funcţii se numeste supraâncărcare (overloading). Două funcţii pot avea acelaşi nume, dar trebuie sa se diferenţieze prin numărul de parametri sau, la număr egal de parametri, sa difere tipul acestora.

Observaţie: nu se poate folosi acelaşi nume pentru două funcţii care diferă numai prin valoarea returnată.

Evident, constructorul nici nu are valoare returnată, pentru ca el nu este apelat de catre o alta funcţie, el este apelat automat de sistem în momentul creării obiectului.

Observatie: chiar daca constructorul este prima functie apelata din cadrul obiectului, în cadrul constructorului se poate face referire la alte functii din obiect. De exemplu, în cel de-al doilea constructor s-a utilizat apelul: BirthDateString = birthDate. Acest apel demonstrează că obiectul este deja format în momentul apelului constructorului.

Supraâncărcarea se utilizează şi la metodele obişnuite ale clasei, nu numai pentru constructori.

2.3.5 Domeniul de vizibilitate pentru componentele unei clase

Fiecare componentă a unei clase are definit un domeniu de vizibilitate în funcţie de care, acea componentă poate fi accesată sau nu din alte clase.In continuare sunt date principalele domenii de vizibilitate ce pot fi definite in C#:

Public: o componentă publică poate fi accesată atât din interior, cât şi din exterior, este vizibilă în toate situaţiile:

Version no.: 2 Page 14 of 82

Page 15: Programare Visual Studio .NET Versiunea 2 Nov 2011

Private: este o componentă ascunsă, poate fi utilizată doar în cadrul clasei, nici măcar clasa copil ce moşteneste clasa de bază nu poate utiliza variabila privată. Prin clasa copil întelegem o altă clasă creată prin derivare din clasa de bază şi care moşteneste toate componentele clasei parinte, binenţeles excluzând componentele private.

Protected: vizibilă în clasa copil, dar nu este vizibilă in exteriorul clasei.

Internal: vizibilă în aplicaţia curentă (DDL-ul curent), dar nu este accesibilă din alte aplicaţii.

Protected Internal: reprezintă intersecţia între cele două domenii de vizibilitate, componenta este vizibilă doar în clasele derivate şi în acelaşi DDL.

Version no.: 2 Page 15 of 82

Page 16: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.3.6 Domenii de vizibilitate pentru o clasă

Dacă clasele sunt create în interiorul altor clase, atunci pot avea toate domeniile de vizibilitate definite mai sus.Dacă clasele sunt definite direct într-un "namespace", atunci ele pot avea atributele internal sau public. Implicit o clasă are atributul internal, deci vizibilă doar în proiectul din care face parte.Namespace-ul reprezintă un spaţiu de clase şi alte componente în interiorul căruia toate aceste componente sunt accesibile reciproc. In afara acestui spaţiu componentele nu sunt vizibile decât dacă acel namespace este adaugat la referinţele proiectului şi inclus în secţiunea "using" (similar cu secţiunea "include" din C clasic).

Version no.: 2 Page 16 of 82

Page 17: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.4 Conceptele Programării Orientate Obiect

Sunt patru concepte de bază pe care se construieşte POO : Abstractizare Incapsulare Moştenire Polimorfism

2.4.1 Abstractizare

Abstractizarea reprezintă posibilitatea ca un program să ignore unele aspecte ale informaţiei pe care o manipulează, adică posibilitatea de a se concentra asupra esenţialului. Fiecare obiect în sistem are rolul unui “actor” abstract, care poate executa acţiuni, îşi poate modifica şi comunica starea şi poate comunica cu alte obiecte din sistem fără a dezvălui cum au fost implementate acele facilităţi.

POO implementează abstractizarea prin intermediul claselor. Clasele definesc proprietăţi şi metode pentru un obiect de un anumit tip. De exemplu, puteam crea abstractizarea unui căţel prin definirea proprietăţilor (culoare, înalţime, greutate) şi a acţiunilor (aleargă, muşcă).

Clasele reprezintă tipare pentru obiectele reale, iar obiectele reprezintă instanţe ale clasei. Clasa este unică, în timp ce se pot crea mai multe obiecte din aceeaşi clasă, fiecare obiect având o durată de viaţă determinată de contextul în care a fost creat.

2.4.1.1 Referinţa obiectelor

Deşi programatorul are acces la membrii unui obiect prin intermediul numelui obiectului, nu trebuie confundată denumirea cu obiectul în sine. Denumirea reprezintă doar o referinţă către zona de memorie unde s-a creat obiectul respectiv. Obiectele sunt plasate într-o stivă (heap) de obiecte în momentul creării lor şi sunt eliminate când obiectele sunt distruse. De regulă, un obiect are o singură referinţă, dar sunt situaţii când pot exista mai multe referinţe la acelaşi obiect. Aceste cazuri apar când se realizează atribuiri între obiecte. De fapt, atribuirile nu se realizează între obiecte, ci între referintele către obiecte.

Putem exemplifica prin câteva linii de cod în clasa Program:

#region Atribuire de referintePerson persReference1, persReference2;persReference1 = new Person("Marcel","Chirica");persReference2 = new Person("Viorel", "Butnaru");Console.WriteLine(" Persoana persReference1 se numeste: {0} ", persReference1.GetNameSurname()); Console.WriteLine(" Persoana persReference2 se numeste: {0} ", persReference2.GetNameSurname());//atribuire de referintepersReference1 = persReference2;

Console.WriteLine(" Dupa atribuire, Persoana persReference1 se numeste: {0} ", persReference1.GetNameSurname());

Console.WriteLine(" Dupa atribuire, Persoana persReference2 se numeste: {0} ", persReference2.GetNameSurname());

#endregion

Rezultatul rulării acestui cod este următorul:

Version no.: 2 Page 17 of 82

Page 18: Programare Visual Studio .NET Versiunea 2 Nov 2011

Observaţie: după atribuirea persReference1 = persReference2 , obiectul persReference1 este pierdut definitiv deoarece nu mai există nici o referinţă la el.

De reţinut că referinţa nu reprezintă obiectul în sine, ci doar adresa acestuia, este similară pointerului definit în C-ul clasic.

2.4.2 Incapsulare

2.4.2.1 Conceptul de încapsulare

Incapsularea este numită şi ascunderea de informaţii în sensul ca asigură faptul că obiectele nu pot schimba starea internă a altor obiecte în mod direct (ci doar prin metode puse la dispoziţie de obiectul respectiv); doar metodele proprii ale obiectului pot accesa starea acestuia. Fiecare tip de obiect expune o interfaţă pentru celelalte obiecte care specifică modul cum acele obiecte pot interacţiona cu el. In spatele interfeţei obiectul poate fi privit ca o cutie neagră (black box) ce realizează taskurile afişate prin interfaţă, dar ascunde detaliile acestor implementări.Sunt trei componente ale unui obiect ce implementează conceptul de încapsulare:

Interfaţa obiectului: interfaţa reprezintă un set de proprietăţi, metode, variabile şi evenimente declarate publice în interiorul obiectului. Orice obiect din afară are acces doar la aceste câmpuri declarate în interfaţă.

Interfaţa arată ca o clasă, dar fără codul efectiv din spatele ei. Chiar dacă nu au cod, ele sunt importante pentru că definesc contractul pe care clasa respectivă trebuie să-l respecte. Sunt foarte utile atunci când se începe construcţia unui proiect mai mare şi în faza de început se definesc aceste interfeţe între obiecte.

Implementarea sau comportamentul obiectului: reprezintă codul efectiv ce realizează taskurile din interfaţă. Acest cod include şi alte câmpuri şi funcţii private, invizibile în exterior, dar apelate în interior pentru efectuarea anumitor sarcini.

Observaţie: programatorul poate schimba oricând codul unui obiect atât timp cât interfaţa rămâne neschimbată; Obiectul va fi folosit în continuare în acelaşi mod, indiferent de modul lui de implementare.

Membrii sau variabilele obiectului: definesc starea obiectului. Obiectele ce aparţin de aceeaşi clasă sunt identice cu excepţia valorilor stocate în variabilele interne. Două obiecte de tip Person au aceeaşi structură, aceeaşi interfaţă, în schimb au nume si date de naştere diferite.

Observaţie: de reţinut că proprietatea nu stochează date, ci doar este un mijloc de a accesa datele stocate într-o variabilă internă.

2.4.2.2 Interfaţa clasei Person

Să modificăm programul nostru prin adăugarea unei interfeţe la clasa Person. Ca regulă de programare, numele interfeţei este data de numele clasei, la care se pune prefixul "I".Se face click dreapta pe proiectul din VS şi se alege opţiunea Add Item/Interface. Numel interfeţei va fi IPerson:

Revenim la clasa Person şi specificăm faptul că această clasă implementează interfaţa IPerson, punând două puncte după definiţia clasei urmate de numele interfeţei implementate::

Version no.: 2 Page 18 of 82

Page 19: Programare Visual Studio .NET Versiunea 2 Nov 2011

public class Person : IPersonDupă această modificare, VS-ul va verifica ca toate componentele definite in interfaţă vor fi implementate în clasă. Deci interfaţa reprezintă contractul, iar clasa implementarea contractului. Avantajul interfetei este că pot s-o utilizez ca un substitut pentru clasa Person în dialogul cu celelalte clase ale programului. De exemplu, funcţia de afişare nu mai primeşte ca parametru clasa Person, ci interfaţa IPerson:

De ce este un avantaj? Pentru că după ce am creat interfeţele, fiecare mebru din echipa de programare poate trece la implementarea codului, utilizând interfaţa unui alt obiect fără ca acel obiect sa fie efectiv implementat. Se poate lucra în paralel prin implementarea simultană a obiectelor.

Un alt avantaj este dat că programatorul nu este lăsat să uite implementarea unei anumite funcţii. Dacă clasa nu include toate componentele interfeţei, atunci VS dă eroare. De exemplu, să adăugăm în interfaţă o nouă proprietate ce returnează vârsta persoanei:

uint GetPersonAge { get; }

Dacă se încearcă execuţia programului în acest caz, obţinem următoarea eroare:

Se adaugă proprietatea în clasa Person şi eroarea dispare:

S-a utilizat câmpul year din structura DateTime şi prin diferenţa lor s-a aflat numărul de ani între ziua de azi şi data naşterii. Este posibil ca persoana respectivă să nu împlinit încă anii (de exemplu, suntem în iunie şi ea face anii în noiembrie). In acest caz facem corecţia scăzând o unitate din numărul anilor.Vârsta persoanei se poate afişa prin modificarea funcţiei DisplayPersonDetails(IPerson pers):

Console.WriteLine("\n\t\t BirthDate = {0} Age = {1}", pers.BirthDateString, pers.GetPersonAge);

Observaţie: proprietatea GetPersonAge este de tip "read only" adică lucrează doar într-un singur sens (lipseşte componenta "set" din definiţie). Nu se poate seta vârsta persoanei, ea se calculează întotdeauna în funcţie de ziua curentă.

Version no.: 2 Page 19 of 82

Page 20: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.4.3 Moştenirea

2.4.3.1 Conceptul de moştenire

Moştenirea permite definirea şi crearea unor clase specializate plecând de la clase (generale) deja definite. Noile clase pot împărtăşi (şi extinde) comportamentul lor, fără a fi nevoie de a-l redefini. Aceasta se face de obicei prin gruparea obiectelor în clase şi prin definirea de clase ca extinderi ale unor clase existente. Conceptul de moştenire permite construirea unor clase noi, care păstrează caracteristicile şi comportarea, deci datele şi funcţiile membru, de la una sau mai multe clase definite anterior, numite clase de bază, fiind posibilă redefinirea sau adăugarea unor date şi funcţii noi. O clasă moştenitoare a uneia sau mai multor clase de bază se numeşte clasă derivată (clasă copil). Esenţa moştenirii constă în posibilitatea refolosirii lucrurilor care funcţionează.Moştenirea reprezintă punctul forte al programării pe obiecte deoarece permite într-un mod foarte simplu şi performant reutilizarea codului. Să presupunem că trebuie creată o nouă clasă “Student” prin care să gestionăm situaţia şcolară a studenţilor. Evident, studentul este o persoană, deci toate atributele şi metodele definite pentru o persoană, sunt valabile şi pentru student. In plus, clasa student mai trebuie să memoreze anul de studiu, media scolară pe acel an de studiu, dacă este student cu taxă sau fara taxă, numărul de credite obtinuţe, etc. Deci, din punct de vedere conceptual, clasa student este o extindere a clasei Person.

Mai întai se creează interfaţa IStudent ce extinde interfaţa IPerson:

Mostenirea unei clase se face prin constructia ”:” .

Instrucţiunea "class Student:Person, IStudent" defineşte o nouă clasă “Student” care moşteneşte clasa “Person” şi interfaţa "IStudent". Aceasta înseamnă că, clasa student beneficiază de toate metodele şi membrii non-privaţi din clasa părinte şi trebuie să implementeze metodele şi proprietăţile delcarate suplimentar în interfaţa "IStudent".

Instanţierea unei clase copil se face prin acelaşi mecanism ca la o clasă independentă:

Student stud1 = new Student("Alina","Ciubotaru","07-Nov-1985"); DisplayPersonDetails(stud1);

Observaţie: funcţia " private static void DisplayPersonDetails(IPerson pers)" din clasa "Program" primeşte ca parametru o instanţă a interfeţei "IPerson" (de fapt o instanţă a unei clase derivată

Version no.: 2 Page 20 of 82

Page 21: Programare Visual Studio .NET Versiunea 2 Nov 2011

din IPerson, interfaţa nu poate fi instanţiată). Totuşi, această functîe s-a apelat cu o instanţă a clasei Student (DisplayPersonDetails(stud1);) şi programul a rulat fără erori. Concluzie: în locul referinţei la clasa de bază se poate utiliza referinţa clasei derivate, deoarece clasa derivată conţine toate câmpurile clasei de bază. Evident, reciproca nu este adevărată. Dacă încercăm să apelăm funcţia cu o instanţa a clasei Person în timp ce ea aşteaptă o referinţă la clasa Student, se obţine eroare de compilare:

Constructorii clasei de bază trebuie rescrişi deoarece clasa “Student” are mai mulţi membri care trebuie initializaţi. In schimb, constructorul clasei copil apelează constructorul clasei părinte:

Explicit, când se specifică care constructor din clasa parinte se apelează (:base(name,surname,birthDate))

public Student(string name, string surname, string birthDate) :base(name,surname,birthDate)

Implicit, când se apelează constructorul implicit al clasei părinte: public Student(string name, string surname) { Name = name; Surname = surname; Burse = BurseType.FaraBursa; Address = string.Empty;

Class = "0000"; }

Intotdeauna la instanţierea unei clase copil se apelează mai întâi constructorul clasei părinte. Acest lucru se poate demonstra uşor dacă punem câte un mesaj în fiecare constructor şi urmărim ordinea de afişare a acestor mesaje:

Fereastra consolă afişează cele două mesaje din constructori în ordinea părinte-copil:

2.4.3.2 Suprascriere (override)

In clasa copil se pot accesa toate metodele, câmpurile şi proprietăţile non-private din clasa de bază. De exemplu, constructorul clasei Student setează proprietatea "Name" ca şi cum aceasta ar fi o proprietate internă clasei. "Name" nu a fost definită în Student, dar este definită în clasa părinte "Person", deci este accesibilă în clasa copil.

Version no.: 2 Page 21 of 82

Page 22: Programare Visual Studio .NET Versiunea 2 Nov 2011

Totuşi, există necesitatea ca anumite metode să fie suprascrise în clasa copil. Să spunem că în clasa de bază Person avem o funcţie "GetPersonQuality" prin care aş vrea să afişez numele persoanei şi calitatea pe care o detine în instituţie (student, profesor, administrator, etc). Clasa de bază "Person" nu are nici o calitate, deci funcţia "GetPersonQuality" pentru aceasta clasă va aduce numai numele persoanei:

In clasa Student, respectiva funcţie se suprascrie, astfel incât să afişeze calitatea studentului:

Prin rularea programului, clasa Program va apela funcţia GetPersonQuality specifica instanţei primite:

In Consolă putem observa rezultatele; pentru Ionuţ, care este persoană funcţia afişează doar numele, iar pentru Alina afişează şi grupa din care face parte (în cazul de faţă grupa 0000).

Nu orice funcţie din clasa de bază poate fi suprascrisă în clasa copil, ci doar dacă este declarată cu atributul "virtual". In clasa copil metoda se suprascrie prin folosirea atributului "override".

2.4.3.3 Identificatorii “this” şi "base"

"This" este o metodă specifică tuturor obiectelor şi care returnează o referinţă la acel obiect. Aşa cum din afara clasei accesăm componentele interne prin numele obiectului (pers.GetPersonQuality()), în interiorul acestuia le accesăm cu identificatorul “this” (this.Surname = surname). Binenţeles, utilizarea lui “this” este oarecum superfluă, putem avea acces foarte bine la componentele interne şi fără “this”.

2.4.3.3.1 "This" separă componentele interne de parametrii cu acelaşi nume

Totuşi este utilă această referinţă când există într-o funcţie dintr-o clasa variabile locale sau parametri care au acelaşi nume cu al componentelor din clasă (cu toate ca acest lucru nu e bine să se întâmple). In acest caz se utilizează “this” pentru a accesa componentele clasei în contrast cu variabilele locale. De exemplu, avem o clasă ce conţine variabilele private "Name" si "Surname" şi o funcţie care primeşte aceste valori prin parametri şi setează variabilele locale cu valorile primite:

Version no.: 2 Page 22 of 82

Page 23: Programare Visual Studio .NET Versiunea 2 Nov 2011

Instrucţiunea "this.Name = Name" atribuie variabilei interne din clasa numită "Name" valoarea pe care o primeşte prin parametrul "Name". Cele două variabile au acelaşi nume, de aceea folosim "this" pentru identificare.

2.4.3.3.2 "This" trimite referinţa obiectului curent

De asemenea, "this" este utilizat pentru a transmite referinţa obiectului curent la funcţiile ce primesc ca parametru un astfel de obiect. De exemplu, aş dori să apelez funcţia de afişare la Consolă "DisplayPersonDetails" direct din constructorul clasei "Person". Funcţia primeşte ca parametru o referinţă la clasa Person, referinţă pe care eu o trimit din interiorul acestui obiect prin identificatorul "this".Facem publică funcţia "DisplayPersonDetails" ca s-o pot accesa din afară şi apelăm funcţia din constructorul clasei "Person":

Observăm diferenţa: din clasa "Program" funcţia se apelează cu referinţa la clasa Person (DisplayPersonDetails(pers2)), iar din clasa "Person" se apelează cu "this": Program.DisplayPersonDetails(this).

2.4.3.3.3 "This" identifică funcţia suprascrisă în clasa derivată

Observăm că funcţia "GetPersonQuality" are implementări diferite în clasa părinte şi clasa copil. Putem apela fiecare funcţie separat dacă specificăm obiectul din care face parte funcţia prin identificatorul "this" sau "base".De exemplu, doresc ca funcţia "GetPersonQuality" să afişeze calitatea obiectului părinte la care adaug noua calitate obţinută de clasa copil:

Sunt în funcţia "GetPersonQuality" din clasa Student şi apelez funcţia "GetPersonQuality" din clasa părinte pentru a obţine calitatea clasei "Person". Accesarea funcţiei din clasa de bază se face cu identificatorul "base".Dacă aş înlocui "base" cu "this" aş obţine un apel recurent la infinit ce dă stiva peste cap:

Version no.: 2 Page 23 of 82

Page 24: Programare Visual Studio .NET Versiunea 2 Nov 2011

In constructorul clasei "Student" pot folosi identificatorii "this"şi "base" pentru membrii clasei fără să am erori de compilare. "Name"-ul îl setez folosind clasa de bază, "Surname" folosind obiectul derivat.

Rezultatul este acelaşi, deoarece acel membru (Name, Surname) are o singură locaţie în memorie şi indiferent cum este accesat, prin pointerul clasei de bază sau pointerul clasei copil, se ajunge în aceeaşi locaţie de memorie.

2.4.3.3.4 Ascunderea funcţiilor din clasa de bază

Chiar dacă funcţia nu e declarată "virtual" în clasa de bază, ea poate fi suprascrisă în clasa derivată: pur şi simplu se implementează aceeaşi funcţie cu un alt cod. Utilizatorii clasei derivate nu vor vedea deloc funcţia din clasă de bază, ei vor avea acces doar la implementarea din clasa derivată. Dar în interiorul clasei derivate, putem accesa funcţia din clasa părinte folosind identificatorul "base".

2.4.3.4 Metode şi variabile staticeAm observat că se poate declara un obiect şi fără crearea unei instanţe în memorie (de exemplu Person pers;), dar până nu se alocă spaţiu de memorie pentru acel obiect efectiv prin comanda new, obiectul nu poate fi utilizat.

Dacă se incearcă apelul unei metode din obiectul declarat , dar neinstanţiat:

C-ul dă eroare la compilare: “Use of unassigned local variable 'pers' “.

Version no.: 2 Page 24 of 82

Page 25: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.4.3.4.1 Metode statice

Si totuşi, se pot accesa metode sau variabile , chiar dacă nu a fost creat obiectul. Aceste variabile trebuie declarate “static” şi ele sunt create într-o zonă specială de memorie la declaratia clasei. Am văzut căîn clasa "Program" ambele funcţii (Main , DisplayPersonDetails) au fost declarate static. Nimeni nu a creat vreo instanţă pentru clasa "Program" şi totuşi funcţiile au fost apelate şi rulate.

O funcţie se declară statică atunci când nu este legată de o instanţă anume a unui obiect, este o funcţie de uz general şi dă acelaşi rezultat indiferent de obiectul din care s-ar apela. De exemplu, putem declara în clasa “Person” o metodă ce returnează data curentă. Funcţia nu are nici o legătură cu o persoană anume, ea pur şi simplu returnează data calendaristică, indiferent de numele persoanelor instanţiate, de aceea declarăm această funcţie de tip "static".

In clasa "Program" apelăm direct funcţia folosind numele clasei şi nu o instanţă de obiect: Console.WriteLine("Today is " + Person.GetCurrentDate());.

Observatie: nu se pot apela funcţii statice folosind referinţa obiectelor:

2.4.3.4.2 Variabile statice

Variabilele statice au acelaşi statut ca şi metodele statice descrise mai sus. De exemplu, se declară o variabilă “currentDate” în clasa “Person” de tip static:

private static string currentDate = string.Empty;

şi apoi funcţile cu care să accesăm această variabilă prin intermediul instanţei unui obiect:

Modificăm pe rând valoarea variabilei prin două obiecte separate ale clasei Person şi afişăm de fiecare dată currentDate-ul:

Version no.: 2 Page 25 of 82

Page 26: Programare Visual Studio .NET Versiunea 2 Nov 2011

Rezultatul este dat mai jos:

Se poate observa că, după ce pers2 a schimbat valoarea la currentDate, acea valoare s-a transferat şi la person1, chiar dacă person1 este obiect separat.Concluzie: varabilele statice au doar o singură instanţă în memorie, iar dacă se modifică valoarea prin intermediul unui obiect, această modificare apare la toate obiectele.

Observaţie: mai jos sunt date câteva aspecte particulare ale funcţiilor statice: O funcţie statică nu poate avea unul din următoarele atribute: override, abstract, virtual Nu se pot apela funcţiile statice din clasa părinte folosind identificatorul base. Trebuie folosit

numele clasei ca identificator. Intr-o clasă statică putem accesa doar membrii statici ai clasei

2.4.3.5 Clase abstracte

O clasă declarată abstractă este un tip special de clasă. Pe lângă componentele obişnuite oricărei clase, aceasta conţine componente declarate abstracte. Membrii abstracţi sunt metode sau proprietăţi ce nu au definită implementarea în clasă, doar amprenta funcţiei (exact ca intr-o interfaţă). Ca şi interfaţa, aceste clase nu pot fi instanţiate, ci doar moştenite, iar clasa derivată trebuie sa implementeze toţi membrii abstracţi ai clasei de bază.

Cea mai faimoasă clasă abstractă este clasa "Object" (sau "object", este acelaşi lucru). Reprezintă clasa de bază pentru toate clasele din C#, implicit, orice clasă deriva din clasa Object, dacă nu se specifică altă clasă de bază.

2.4.3.5.1 Diferenţe între clasele abstracte şi interfeţe

Interfeţele sunt similare cu clasel abstracte, dar cu unele diferenţe: O clasă abstractă poate conţine şi metode sau proprietăţi ce nu sunt abstracte, au codul

implementat, interfaţa conţine doar membri abstracţi. In C# o clasă poate să moştenească doar o singură clasă de bază abstractă, dar mai multe

interfeţe O interfaţă nu poate conţine constante, câmpuri, constructori, operatori, etc, ea reprezintă doar un

contract abstract pe care clasele derivate trebuie să-l implementeze. Interfeţele nu pot avea membri statici, sau atribute de tipul abstract, public, protected, internal,

private, virtual, override deoarece nu au sens în acest context.

2.4.4 Polimorfismul

Version no.: 2 Page 26 of 82

Page 27: Programare Visual Studio .NET Versiunea 2 Nov 2011

Polimorfismul este abilitatea de a procesa obiectele în mod diferit, în funcţie de tipul sau de clasa lor. Mai exact, este abilitatea de a redefini metode pentru clasele derivate. De exemplu, pentru o clasă Figura putem defini o metodă arie. Dacă Cerc, Dreptunghi, etc. vor extinde clasa Figura, acestea pot redefini metoda arie.

Analizând exemplul nostru, avem polimorfism în cazul funcţiei "GetPersonQuality()". In clasa de bază "Person" aceasta are o implementare, iar în clasa derivată aceeaşi funcţie are un alt cod. Ambele funcţii sunt apelate în clasa Program de metoda "public static void DisplayPersonDetails(Person pers)". Observăm ca metoda primeşte ca parametru clasa e bază, dar în momentul apelului, referinţa "pers" primită poate fi foarte bine o referinţă la clasa "Student". In primul caz se apelează metoda din clasa de bază cu un anume efect, în al doilea caz, se apelează metoda din clasa Student cu un alt efect.

Din clasa Person am mai putea deriva o alta clasa "Profesor" ce implementează în mod diferit funcţia "GetPersonQuality()". Referinţa la clasa "Profesor" trimisă metodei "DisplayPersonDetails (Person pers)" ar avea alt efect decât primele două. Deci prin polimorfism înţelegem că acelaşi obiect se poate comporta diferit în funcţie de contextul în care este folosit (dacă se utilizează clasa de bază sau o anumită clasă derivată).

Version no.: 2 Page 27 of 82

Page 28: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.5 Aspecte ale POO în mediul de dezvoltare Microsoft Visual Studio

Programarea Orientată Obiect este un concept ce a fost implementat de mai multe limbaje de programare: C++, Java, Pascal, C#, etc. Toate aceste limbaje suportă conceptele definite de POO, dar fiecare are anumite particularităţi ce nu ţin de standardul POO.

In acest capitol sunt date câteva particularităţi ale dezvoltării programelor POO în mediul Visual Studio.

2.5.1 Garbage collection

2.5.1.1 Eliberarea memoriei în mod automat

Garbage Collection reprezintă o soluţie la o problemă veche în lumea programatorilor POO: curăţarea memoriei de obiectele ce nu mai sunt folosite. Atunci când se creeză un nou obiect prin comanda "new", acesta va fi stocat în memorie şi apoi utilizat. După utilizare trebuie dată comanda de distrugere a obiectului pentru a elibera memoria. In caz că se uită acest aspect, atunci se constată că pe parcursul rulării programului, acesta creşte în dimensiune până ocupă toată memoria disponibilă. Este uşor să uiţi despre distrugerea unui obiect, mai ales când referinţa acestuia este dată ca parametru în tot felul de funcţii şi se pierde urma acestuia. De aceea, în VS rulează programul Garbage Collection (GC).

GC este un program ce rulează în background, ce are ca scop eliminarea din memorie a obiectelor care nu mai sunt folosite. Programul eliberează memoria de obiecte inutile, rearanjează obiectele valide în memorie şi eliberează astfel resursele sistemului.

Procesul GC se declanşează periodic în mod automat dar poate fi startat şi de către utilizator. In principiu, el se declanşează automat când apare una din situaţiile:

un obiect rămâne fără referinţă stiva de obiecte a ajuns la saturaţie

In momentul distrugerii unui obiect, GC-ul apelează destructorul obiectului respectiv. Dacă această metodă nu a fost suprascrisă, atunci obiectul este distrus în mod direct. Destructorul implicit eliberează doar memoria gestionată de C#, el nu poate elibera resursele externe alocate explicit de către programator: fişiere, conexiuni la baze de date, etc. In acest caz, sunt necesare anumite operaţii înainte de distrugerea obiectului (închiderea conexiunii la baza de date, eliminarea altor obiecte conexe, etc), şi programatorul trebuie să suprascrie destructorul clasei şi execută aceste taskuri.

Nu se stie niciodată precis când se declanşează procesul de curăţire a stivei de către GC, însă se poate forţa pornirea acestei operaţii prin apelarea funcţiei "GC.Collect()".

Exemplificăm aceste concepte prin adăugarea destructorului în clasa "Person":

In clasa "Program" realizăm o funţie (CreateDestroyPerson())ce creează un obiect de tip Person, apoi la ieşirea din funcţie se apelează GC.Collect:

Version no.: 2 Page 28 of 82

Page 29: Programare Visual Studio .NET Versiunea 2 Nov 2011

Rezultatul acestei operaţii se vede la concolă:

Se observă că, GC.Collect distruge obiectele ce nu mai sunt necesare în acel moment, şi anume, obiectul creat în funcţia "CreateDestroyPerson()". Acel obiect este intern funcţiei, deci la ieşirea din această funcţie, toată memoria alocată în interiorul ei trebuie eliberată, deci aptă pentru Garbage Collection.

2.5.1.2 Eliberarea memoriei la cerere

Atunci când un obiect creează alte obiecte ce nu pot fi procesate de GC (conexiuni la baza de date, deschiderea de fisiere), este bine ca distrugerea unui astfel de obiect să se facă în mod controlat, de către programator. In acest scop programatorul are la dispoziţie o interfaţă "IDisposable" ce conţine o singură funcţie: "Dispose()". O clasă ce moşteneste această interfaţa trebuie să implementeze funcţia respectivă. Această funcţie se apelează explicit de către programator atunci cand obiectul nu mai este necesar, sau implicit când se creează obiectul într-o secţiune de tip "using".Modificăm clasa Person astfel încât să implementeze funcţia Dispose:

Iar în clasa Program creăm o persoană prin clauză "using":

Când se termină blocul "using" se apelează automat funcţia "Dispose" a obiectului creat pentru eliberarea resurselor alocate în acel obiect. Analizăm mesajele afişate la consolă şi observăm că obiectul "Ionut Popescu" s-a distrus imediat după închiderea blocului "using":

Version no.: 2 Page 29 of 82

Page 30: Programare Visual Studio .NET Versiunea 2 Nov 2011

2.5.2 Excepţii şi tratarea excepţiilor

Sunt situaţii când parcurgerea normală a programului se întrerupe din cauza unor erori neprevăzute (împărţire la zero, conexiunea cu baza de date nu mai funcţionează, scrierea în fişier blocată deoarece acesta nu există sau este ReadOnly), şi în acel moment apare pe ecran un mesaj de eroare după care execuţia programului se suspendă.

De exemplu, să luăm cazul unei împărţiri la zero: se adaugă în clasa "Program" o funcţie ce returnează rezultatul împărţirii a două numere:

Rezultatul apelului este dat in continuare:

Se observă că primul apel 3/2 a returnat rezultatul corect (bine, dacă nu ţinem cont de zecimale), cel de-al doilea: 3/0 a produs eroare care a blocat programul.

Evident că funcţia este scrisă greşit, ar trebui că orice împărţire la zero să fie exclusă printr-un if simplu, dar nu în toate cazurile se pot scrie variante de "if " care să excludă toate condiţiile de eroare. De aceea, VS-ul pune la dispoziţia programatorilor un sistem simplu şi performant de tratare a excepţiilor, astfel încât, acestea să nu ducă la blocarea programului. Acest mecanism de prindere a excepţiilor este format din structura try…catch…finally:

Version no.: 2 Page 30 of 82

Page 31: Programare Visual Studio .NET Versiunea 2 Nov 2011

In secţiunea “try” se introduce codul pasibil de erori iar secţiunea “catch” este blocul ce tratează aceste erori. In această secţiune se intră doar dacă a apărut o excepţie. In cazurile în care sunt anumite acţiuni ce trebuie executate (închidere de fişiere, conexiuni, etc), indiferent dacă s-a produs sau nu o excepţie, programatorul poate insera în secţiunea “finally” codul pentru închiderea acestor resurse. Sectiunea “finally” se executa întotdeauna, direct dupa secţiunea “try” dacă nu au fost erori, sau după “catch” dacă s-a produs o eroare. Programul rulează secţiunea “try”, iar dacă o instrucţiune din aceasta secţiune produce eroare, imediat după acea instrucţiune se sare la secţiunea “catch”. Instrucţiunile din secţiunea “try” ce urmează după instrucţiunea de eroare nu se mai execută, de aceea este necesar capitolul “finally” care sa faca curaţenie, indiferent dacă s-a produs sau nu eroare.

In exemplul următor se arată necesitatea includerii secţiunii finally în structura "try". Instrucţiunea "myFile.Close();" trebuie executată în mod obligatoriu, indiferent ce eroare se întâmplă pe parcurs, în caz contrar, acest fişier va rămâne blocat în Windows şi nu mai poate fi accesat de alte programe.

In secţiunea “catch” este accesibil obiectul “Exception” ce include informaţii referitoare la cauza erorii (în cazul de faţă am utilizat membrul "Message" pentru a afişa la consolă textul erorii.

Version no.: 2 Page 31 of 82

Page 32: Programare Visual Studio .NET Versiunea 2 Nov 2011

3 Dezvoltarea aplicaţiilor de tip GUI in VS.NET

3.1 Etapele dezvoltării unei aplicaţii Windows în VS.NET

Până acum am utilizat proiecte de tip "Console" pentru exemplificarea conceptelor programării pe obiect. Sunt cele mai simple proiecte, se aseamănă cu aplicaţiile dezvoltate în limbajele clasice, dar nu au deloc o interfaţă grafică performantă. Nu se regăsesc imagini, butoane sau alte controale grafice cu care suntem obişnuiţi într-o aplicaţie de tip "Windows".

De aceea, VS.NET pune la dispoziţie şi alte tipuri de proiecte, unde programatorul dispune de un spectru foarte larg de clase, sub forma unor controale vizuale, cu ajutorul cărora poate să construiască într-un timp redus aplicaţii cu interfeţe grafice performante. Aceste controale includ funcţionalitatea necesară pentru marea majoritate a programelor, de aceea ele au devenit un fel de standard în domeniul “programării vizuale”.

Programarea VS.NET nu este o programare efectiv vizuală în conceptul dezvoltat de National Instruments prin tehnologia LabView, în care programatorul nu introduce linii de cod, ci dezvoltă programul doar prin operaţii “drag&drop”. Totuşi, programarea în VS.NET se apropie foarte mult de acest concept, cea mai mare parte a codului unui program fiind inclus automat de VS.NET prin inserarea “drag&drop” a controalelor vizuale, programatorul trebuie să se ocupe doar de tratarea evenimentelor specifice acestor controale. Paşii pe care trebuie să-i parcurgă un programator pentru dezvoltarea unei aplicaţii Windows standard sunt următorii:

se deschide o formă de lucru adaptată tipului de aplicaţie (Windows application, WEB, DLL, etc); se poziţionează cu ajutorul mouse-ului controalele grafice ce realizează interfaţa între aplicaţie şi

utilizator. Din fereastra “Properties” a controlului se setează starea acestuia (comportamentul său static);

se scrie cod pentru tratarea evenimentelor specifice fiecărui control în parte (comportament dinamic);

Version no.: 2 Page 32 of 82

Page 33: Programare Visual Studio .NET Versiunea 2 Nov 2011

3.2 Prima aplicatie Windows: "Hello world"

Pentru a exemplifica modul de programare în VS.NET se va crea o aplicaţie grafică care să afişeze pe ecran mesajul “Hello world” la apăsarea unui buton.

Se deschide un nou proiect (Open/Project/Windows Application) şi se setează denumirea acestuia: "Hello".

Automat VS-ul creează o formă numită “Form1” în care se vor depune toate controalele vizuale necesare aplicaţiei. Fără a scrie nici o linie de cod, am obţinut deja o aplicaţie funcţională care afişeză pe ecran o fereastră numită “Form1” ce răspunde la o mulţime de evenimente venite din partea sistemului: mărire, micşorare, închidere, redimensionare, etc.

De fapt, aplicaţia are destule linii de cod ce au fost introduse automat de VS în momentul creării proiectului: este inserată o pagină “Program.cs” (ca şi la proiectul de tip Console Application) ce

include codul de startare a programului:

Observaţie: "[STAThread]" este un atribut ce setează apartamentul threadului curent la tipul “single threaded”. Are sens numai dacă în program se apelează obiecte de tip COM.

Version no.: 2 Page 33 of 82

Page 34: Programare Visual Studio .NET Versiunea 2 Nov 2011

Implicit se scrie o clasa “Program” care conţine funcţia “Main” ce reprezintă punctul de intrare în program. Folosind funcţii statice din clasa Application, functia Main face anumit einiţializări valabile în toată aplicaţia şi apoi creează un obiect din clasa “Form1” pe care-l lansează în execuţie:

Application.Run(new Form1());

Tot VS-ul creează automat o clasă numită “Form1” în care programatorul va insera controalele care să asigure interfaţa între program şi utilizator. Clasa “Form1” nu conţine iniţial nici un control, este o fereastra simplă, dar care asigură toată funcţionalitatea unei ferestre de program.

De unde ştie clasa noastră “Form1” să răspundă la diverse acţiuni ale mouse-ului din moment ce noi nu am scris nici o linie de cod ? Simplu, în definiţia clasei “Form1” se observă că ea moşteneşte clasa “Form” (public partial class Form1 : Form) care la rândul ei moşteneşte clasa “Panel” care moşteneşte clasa “Mobile control” şi tot aşa… una din aceste clase superioare include metode care să raspundă la acţiunile mouse-ului. In constructorul clasei “Form1” se apelează funcţia “InitializeComponent();”. Această funcţie este scrisă automat de VS în funcţie de controalele pe care programatorul le inserează în formă prin operaţia “drag&drop”. Initial, neavând nici o componenta în formă, funcţia de iniţializare se ocupă doar de setările formei (setează titlul fereastrei, iniţializează lista "components" unde va stoca controalele formei):

Personalizăm forma, prin adaugarea unei etichete (label) în forma şi un buton. La click pe buton va aparea în eticheta textul “Hello World”.

Version no.: 2 Page 34 of 82

Page 35: Programare Visual Studio .NET Versiunea 2 Nov 2011

Din fereastra "Tool Box" din stânga (dacă nu este vizibilă, atunci se deschide din meniul View/Toolbox) se aduce prin drag&drop două controale: etichetă (label) şi buton (button).

Se selectează etichetă şi se accesează fereastra de proprietăţi ale acesteia. Pentru mai mult efect, se inserează o imagine în back-ground-ul etichetei, "autosize" = false pentru a putea modifica dimensiunea, se setează mărimea fontulului şi culoarea:

Se selectează butonul şi similar i se ataşează textul "Say Hello". Se intră în fereastra de proprietăţi, pagina de evenimente şi se face double click pe evenimentul "Click":

Version no.: 2 Page 35 of 82

Page 36: Programare Visual Studio .NET Versiunea 2 Nov 2011

In felul acesta se setează funcţia "button1_Click" care se execută în momentul când utilizatorul face "click" pe buton.

După aceste operaţiuni, codul din programul nostru va arata astfel:

Funcţia “InitializeComponent()” se modifică automat în funcţie de controalele adăugate de noi şi de setările iniţiale date acestora (culoare, font, dimensiuni, etc).

Singura funcţie scrisă de programator este handler-ul evenimentului “button1.Click” în care se specifică acţiunea ce se execută la acţionarea butonului “button1”:

3.2.1 Funcţia InitializeComponent

Version no.: 2 Page 36 of 82

Page 37: Programare Visual Studio .NET Versiunea 2 Nov 2011

Această funcţie este scrisă automat de VS şi se apelează în constructorul clasei Form1. Se construieşte prin intemediul design-erului, orice modificare pe care noi o facem în design (adaugarea unui control, schimbarea unor proprietăţi, etc) se traduce automat prin cod adăugat în funcţie.

In prima parte a functiei se creeaza un obiect “resources” de tipul ComponentResourceManager prin intermediul căruia se va gestiona setul de controale inserate în forma. Apoi se instanţiază pe rând câte un obiect pentru fiecare control inserat de noi cu ajutorul mouse-ului în forma de lucru. In cazul de faţă sunt numai 2 controale:

După instanţiere sunt setate proprietăţile acestor controale, în funcţie de modificările efectuate de noi în cadrul design-erului:

…………..this.button1.Location = new System.Drawing.Point(93, 189);

this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23);

……………

3.2.2 Interceptarea evenimentelor

In fereastra de design (Form1.cs [design]) am inserat un buton numit “button1” după care am dat double-click pe el. Automat se intra in fereastra “Form1.cs” şi se creează o funcţie:

private void button1_Click(object sender, EventArgs e) { }

Acesta este funcţia ce se apelează automat când se execută click pe butonul “button1”. De unde ştim acest lucru? Ne uităm în funcţia InitializeComponents() şi vedem ca s-a introdus o linie în plus la capitolul destinat butonului:

this.button1.Click += new System.EventHandler(this.button1_Click);

Instrucţiunea de mai sus specifică ca s-a adăugat un nou “handler” (o nouă funcţie) care sa rezolve evenimentul “button1.click” lansat automat în sistem la acţionarea butonului.

Comentariu:Programarea în Windows este o programare bazată pe evenimente. Este o concepţie diferită faţă

de programarea clasică în care se preia controlul microprocesorului la intrarea în funcţia main şi apoi se merge din funcţie în funcţie până la terminarea programului. In tot acest timp, microprocesorul execută o anumită funcţie sau stă într-o anumită buclă.

Un program scris în Windows nu urmează această regulă. Se intră în funcţia main unde se instanţiază obiectele de lucru, se afişează fereastra de interfaţă cu utilizatorul şi apoi se eliberează microprocesorul. In momentul în care utilizatorul realizează o anumită acţiune (apasă o tastă, acţionează mouse-ul, etc), se produce un eveniment în sistem, eveniment care trebuie procesat corespunzător de programul Windows.

In programul nostru s-a specificat că evenimentul produs de acţionarea butonului (this.button1.Click ) va fi rezolvat de funcţia button1_Click . Funcţia primeşte ca parametri obiectul care a produs evenimentul (object sender), precum şi un obiect de tip EventArgs în care putem găsi informaţii despre evenimentul respectiv.

Version no.: 2 Page 37 of 82

Page 38: Programare Visual Studio .NET Versiunea 2 Nov 2011

Ce trebuie să se întample când se face click pe buton? Acest lucru îl hotărăşte programatorul care dezvoltă programul şi a pus acel buton în fereastra. In cazul nostru, dorim ca la acţionarea butonului să apară un mesaj în eticheta de deasupra butonului:

label1.Text = @"Hello World";

Această instrucţiune este singura linie de cod scrisă de noi în tot acest program, de restul se ocupă VS-ul.

De fapt sunt mult mai multe evenimente pe care aplicaţia noastră le interceptează: închiderea, mărirea, deplasarea ferestrei, evenimentele mouse-ului, etc. Chiar dacă noi nu am scris funcţii care sa trateze aceste evenimente, ele sunt tratate automat de VS.

Sunt mai multe evenimente trimise de sistem către buton la care putem răspunde prin ataşarea unor funcţii corespunzătoare. Setul de evenimente primite de un control sunt vizibile în fereastra de proprietăţi aferentă

acestuia. Fereastra include două pagini de proprietăţi selectabile prin butoanele (proprietatile statice ale

controlului) şi (pagina cu evenimentele asociate controlului).

In figura de mai sus sunt date cele două pagini de proprietati. In pagina cu evenimente se observă că s-au adăugat două handler-e la evenimentele “MouseEnter” (mouse-ul intra în zona butonului), respectiv “button1_MouseEnter” şi “MouseLeave” (mouse-ul părăseşte zona butonului), respectiv “button1_MouseLeave”. Funcţiile s-au adăugat prin double-click în dreptul acestor evenimente din pagina de proprietăţi.

Corpul funcţiilor este format dintr-o singură instrucţiune care schimbă textul afişat de buton în “Mouse enter”, respectiv “Mouse leave”.

Version no.: 2 Page 38 of 82

Page 39: Programare Visual Studio .NET Versiunea 2 Nov 2011

3.2.2.1 Evenimente şi delegaţi. Soluţii cu proiecte multiple

In momentul când utilizatorul face click pe buton, acesta ar trebui să execute o acţiune în clasa container (clasa care-l conţine, in cazul nostru clasa Form1 ce instanţiază acel buton). Dar evident, butonul nu ştie nimic despre clasa container, el este un obiect din biblioteca .NET şi este instanţiat în mii de aplicaţii de tipul "Windows Form".

Tot ce poate să facă un buton în momentul când user-ul face "click", este să lanseze evenimentul "Click". Daca este cineva să-l asculte (adică s-a scris o funcţie care sa prindă evenimentul), atunci acţiunea click va avea afect, altfel evenimentul se pierde în eter. Se poate face o paralelă cu acţiunile unui caţel de pază. Caţelul de pază (ca şi butonul), când se naşte, nu ştie unde va păzi şi cine-l va asculta. Dacă este pus să păzească o vie de exemplu, tot ce poate el, este sa facă gălăgie, adică să emită evenimente (în cazul de faţă evenimentul "Thief_Inside"). Dacă este cineva să-l asculte bine, dacă nu, evenimentele se pierd.

Pentru a putea lansa şi prinde evenimente, trebuie realizate câteva acţiuni: Definirea obiectulului "Delegate": reprezintă amprenta funcţiei care trebuie sa prindă acel

eveniment (echivalent cu un pointer la funcţie din C clasic). Definirea obiectulului "Event": este evenimentul propriu-zis. Lansarea evenimentului Prinderea evenimentului de functia "handler"

Toate aceste acţiuni se fac automat de către VS în momentul când se face double-click pe un buton în fereastra de design. VS-ul creează automat o funcţie (handler) care sa trateze evenimentul trimis de buton şi tot ce avem noi de facut este să introducem cod în acea funcţie. Dacă ne uităm în background (Designer.cs), observăm că în acel fisier, VS-ul introduce cod pentru legarea evenimentului la funcţie:

Putem defini şi noi obiecte de tip Delegate şi apoi să asociem evenimente la aceştia pentru orice tip de dialog între clase, nu neapărat clase de tip UI (User Interface). De exemplu, dorim să afişăm un text în Form1 atunci când se creează o instanţă a clasei Person. In acest sens, trebuie realizate următoarele modificări:

Modificarea clasei Person pentru a trimite evenimente în sistem: Se defineşte un Delegate numit "MessageDisplayDelegate" în namespace-ul "namespace

OOP_Fundamentals" ce primeşte un string şi nu returnează nimic. Se defineşte în clasa Person un eveniment static numit "MessageDisplayEvent"de tipul

acestui delegate.

Se adaugă în constructorul clasei Person cod ce trimite evenimentul având ca mesaj numele obiectului care se creează:

Version no.: 2 Page 39 of 82

Page 40: Programare Visual Studio .NET Versiunea 2 Nov 2011

Adăugarea proiectului "OOP_Fundamentals" la referinţele proiectului "Hello". Cele două proiecte realizate pâna acum ("OOP_Fundamentals" şi "Hello" ) sunt complet

independente, în sensul că, clasele definite într-un proiect, nu sunt vizibile în celalalt proiect. Putem uni cele două proiecte prin adăugarea unuia ca referinţă la celălalt.Click dreapta pe secţiunea "References" din proiectul "Hello" şi se alege opţiunea "Add reference". Din npua fereastra Se deschide tab-ul "Projects" şi se selectează proiectul "OOP_Fundamentals":

Din acest moment, toate clasele publice definite în acest proiect vor fi accesibile în proiectul "Hello".

Trebuie doar ca namespace-ul "OOP_Fundamentals" să fie adăugat la capitolul "using" din header-ul fiecărei clase ce doreşte utilizarea câmpurilor din "OOP_Fundamentals" .

Ataşarea funcţiilor (Handler) la evenimente:

In acest moment, clasa Person trimite evenimente de tip "MessageDisplayEvent" pentru orice obiect nou iniţializat. Dacă instanţiem în clasa "Form1" un obiect de tip Person, ar trebui să primim acest eveniment. Adăugăm o funcţie care să fie apelată la primirea evenimentului:

Version no.: 2 Page 40 of 82

Page 41: Programare Visual Studio .NET Versiunea 2 Nov 2011

Funcţia se numeşte "void DisplayMessage(string text)" şi trebuie să aibă aceeaşi semnătură definită în delegatul "MessageDisplayDelegate". Funcţia deschide un MessageBox ce afişează textul primit ca parametru:

Lansarea evenimentelor:

Adaugăm în clasa Form1 un nou button prin care să creăm noi obiecte de tip Person folosind constructorul ce trimite evenimentul:

In acest moment, la fiecare click pe acest buton, Form1 afişează în MessageBox numele şi prenumele obiectului creat:

3.2.2.2 Handlere multiple

Putem ataşa mai multe funcţii care să trateze un anumit eveniment, nu obligatoriu în aceeaşi clasă. De exemplu, o clasa sursă poate trimite un eveniment ce conţine datele de măsurare a unui senzor la un moment dat. Acel eveniment se interceptează de clasa UI ce afişează pe ecran valoarea primită, de clasa ce salvează în baza de date valorea, dar şi eventual de o clasă care să transmită pe internet valorile la un server central.

Să ataşam de exemplu două handler-e care să intercepteze evenimentul "button1.Click".

Version no.: 2 Page 41 of 82

Page 42: Programare Visual Studio .NET Versiunea 2 Nov 2011

Funcţia "DisplayMessage" (observaţi că are acelaşi nume cu handler-ul evenimentului "Person.MessageDisplayEvent", dar diferă prin numărul şi tipul parametrilor) creează o fereastră de mesaje (MessageBox) ce afişează un text pe ecran. Prima parte a textului afişat este construit din proprietatea “Text” a obiectului care a trimis evenimentul (((Button)sender).Text ) după ce a fost în prealabil convertit la o clasă de tip “Button”.

De ce am convertit obiectul sender la tipul Button? Pentru ca sa putem avea acces la proprietăţile şi funcţiile specifice acestei clase. Fără această conversie nu există prea multe informaţii disponibile despre obiectul ce a trimis evenimentul.

Problema care se pune este ca programatorul trebuie să ştie foarte bine către ce tip face conversia obiectului “sender”. Acesta vine în parametrii functiei sub forma de “object”, adică este o referinţă la clasa “object”, clasa din care derivează toate obiectele din C#. Fără cast-ul respectiv, nu putem avea acces decât la metodele din clasa “object”:

Proprietăţile clasei "Object" în comparaţie cu proprietăţile aceluiaşi Object convertit la tipul "Button"

In figura de mai sus sunt prezentate cele două situaţii: metodele disponibile din clasa părinte “object” şi metodele aceluiaşi obiect “sender” după ce a fost convertit la clasa derivata “Button”.Noi ştim că evenimentul este lansat de obiectul “Button1”, de aceea am convertit sender-ul la tipul “Button”. Dar ce se întâmplă dacă convertim sender-ul la un alt tip , de exemplu la tipul “TextBox” şi să afişăm proprietatea “Text” pentru acest sender de tip TextBox?

Version no.: 2 Page 42 of 82

Page 43: Programare Visual Studio .NET Versiunea 2 Nov 2011

Eroare de conversie de tip "Cast" apărută la rularea programului

Eroarea apare abia la rularea programului, când evenimentul se produce efectiv şi atunci realizează interpretorul că de fapt, sender-ul care a produs evenimentul este un obiect de tip “Button” pe care noi dorim să-l convertim la un “TextBox”, ceea ce evident produce eroare.

Evident, că putem ocoli aceste tipuri de erori apărute în timul rulării. Orice obiect afişează o funcţie "GetType()" prin care se poate afla tipul obiectului în momentul execuţiei. Si aceasta, chiar dacă acel obiect este de tipul "Object", pentru ca funcţia este de tipul virtual în clasa de bază şi suprascrisă în clasa derivată.

Se observă că s-a utilizat delegate-ul standard "public delegate void EventHandler(object sender, System.EventArgs e)" definit în biblioteca Systems pentru interceptarea evenimentului "Click". Acest delegate defineşte o funcţie ce primeşte doi parametri: sender-ul şi argumentul evenimentului (EventArgs). O bună practică în sfera programării C# presupune ca fiecare delegate să includă cele două câmpuri.

3.2.3 Concluzii

In acest subcapitol am văzut cum se construieşte o aplicaţie Windows în C#, cum se inserează controale în formă şi interacţiunea cu aceste controale (interceptarea evenimentelor), am vorbit despre funcţia InitializeComponent scrisă automat de VS, despre clasa “MessageBox”, despre delegaţi şi evenimente.

In continuare vom dezvolta o nouă formă care sa prezinte către utilizator o interfaţă grafică de introducere a datelor specifice unui student: nume, prenume, localitate, forma de cazare in camin, media, etc.

Prin intermediul acestei aplicaţii vom învaţa câteva controale vizuale frevent întâlnite în majoritatea aplicaţiilor Windows: controlul TextBox, CheckBox, GroupBox, RadioButton, ListBox, Timer, ProggresBar … şi vom mai vedea.

Version no.: 2 Page 43 of 82

Page 44: Programare Visual Studio .NET Versiunea 2 Nov 2011

3.3 Utilizarea controalelor standard în VS.NET

In acest capitol vom învăţa câteva controale (clase de obiecte) disponibile în mediul VS.NET şi utilizate în mod frecvent de marea majoritate a aplicaţiilor de tip GUI (Graphic User Interface). Aceste controale sunt grupate în fereastra "Toolbox" vizibilă în stânga ecranului atunci când programatorul deschide fereastra "Design" a formei:

Pornim un nou proiect numit "StudentView" în care dezvoltăm o formă unde vom insera câmpurile necesare pentru vizualizarea şi introducerea datelor specifice unui student. Forma "Form1.cs" creată automat de VS se redenumeşte "StudentView", iar la referinţe se adaugă proiectul "OOP Fundamentals" pentru a putea utiliza clasa "Student" definită în acest proiect.

3.3.1 Controlul TextBox

Caseta de text se utilizează pentru introducerea de informaţii de la tastatură sub formă de text. Este un mini-editor de text în sensul că permite o mulţime de facilităţi întâlnite într-un editor de text obişnuit: copy/paste, delete, scroll bar, scriere pe mai multe rânduri, etc.

Principalele proprietăţi ale unei casete de text sunt rezumate în următoarele: Name: toate obiectele dintr-un program au un nume cu care pot fi identificate şi manipulate; Text: reprezintă textul inserat în editorul respectiv (este dat sub forma de string);

Version no.: 2 Page 44 of 82

Page 45: Programare Visual Studio .NET Versiunea 2 Nov 2011

BackColor/ForeColor: culorile de fundal şi de text pentru caseta respectivă; BorderStyle: stabileste tipul marginii de casetă, sunt disponibile 3 variante: None (fără bordură),

FixedSingle (caseta are bordură simplă), Fixed3D (bordura în relief); Autosize : True/False: dacă mărimea casetei se ajustează automat în funcţie de conţinutul său; Multiline: precizează dacă textul din caseta poate fi afişat pe mai multe rânduri; Cursor: stabileşte forma cursorului când acesta se afla deasupra casetei de text; Enabled: dacă este True atunci utilizatorul poate modifica conţinutul textului, altfel textul este

protejat la scriere; ScroollBars: stabileşte barele de derulare aferente casetei:

o none: fara scroll;o Horizontal: scroll orizontal sub caseta de texto Vertical: scroll vertical

CharacterCasing: o Lower: toate caracterele vor fi convertite la minuscule, indiferent de cum au fost

introduse;o Upper: toate caracterele sunt majuscule;o Normal: caracterele sunt nemodificate;

In forma "StudentView" vom insera controale de tip TextBox pentru patru câmpuri de informaţii necesare în descriere studentului: nume (tbSurname), prenume (tbName), localitate (tbCity), media (tbAverage):

In partea de jos a formei s-a introdus un TextBox (tbView) de tip ReadOnly Multiline cu ScrollBar vertical în care se vor afişa toate informaţiile inserate în câmpurile formei. Aceste informaţii vor fi refresh-ate la fiecare click pe butonul “Vizualizare Informatii” (bView).

Indiferent de câmpul în care se află focus-ul, dorim ca tasta “Enter” să acţioneze butonul “bView”, astfel încât, după orice modificare, utilizatorul sa poată da Enter pentru a vedea rezultatul operaţiunii. Ataşarea unui buton de tasta Enter se face din proprietatile formei. Una din aceste proprietăţi se numeşte “AcceptButton” pe care noi o setăm la butonul “bView”.

3.3.1.1 Validarea informaţiilor din TextBox

Version no.: 2 Page 45 of 82

Page 46: Programare Visual Studio .NET Versiunea 2 Nov 2011

La preluarea datelor de la utilizator trebuie sa ne asigurăm că acestea nu conţin erori de introducere. De exemplu, media studentului trebuie sa fie un număr cuprins între 1 şi 10. Deci sunt două verificari ce trebuie efectuate:

textul introdus sa fie un număr valid (fără litere sau alte caractere diferite de cifre) numărul nu depaşeşte limitele impuse

Verificarea ar fi bine să se facă automat, chiar la părăsirea câmpului tbAverage, astfel încât, utilizatorul să fie întors la editarea câmpului până introduce date valide. Această verificare automată se face pe evenimentul “Validating” asociat controlului tbMedie. Evenimentul se produce când se părăseşte textbox-ul şi are loc operaţia de validare a textului scris în acesta.

S-a utilizat metoda tbMedie.Focus() pentru a aduce mouse-ul în câmpul tbMedie semnalizând astfel utilizatorului care este câmpul cu informaia greşită.

3.3.2 Controlul ToolTip

VS-ul permite ataşarea diverselor mesaje (ToolTip) de un anumit control din formă. Introducem o nouă funcţie "MyInitialization" alături de "InitializeComponents" în care să iniţializăm şi alte compinente din formă, cum ar fi de exemplu, acest ToolTip:

Recapitulare: în acest subcapitol s-a prezentat controlul “TextBox” cu principalele sale proprietăţi. Am deschis un nou proiect în care s-au inserat câteva TexBox-uri pentru introducerea datelor despre un student şi un TextBox multiline pentru afişare. Am aratat cum se pot verifica automat datele dintr-un câmp oarecare şi cum se ataşează un buton de tasta “Enter”.

Version no.: 2 Page 46 of 82

Page 47: Programare Visual Studio .NET Versiunea 2 Nov 2011

3.3.3 Controlul CheckBox (caseta de validare)

CheckBox-urile (casete de validare) sunt utilizate pentru afişarea (sau introducerea) datelor de tip boolean ce pot lua doar două valori “true/false”. In cazul nostru sunt necesare aceste CheckBox-uri pentru semnalizarea unor situaţii în care se află studentul: este cazat sau nu la cămin, primeşte sau nu bursă, este sau nu integralist, etc.

Am adaugat doua CheckBox-uri , cbCazare cu textul “Cazat in camin” si cbBursa cu textul = “Primeste bursa”. Functia “bVizualizare_Click “ s-a modificat pentru a completa informatiile despre student si in functie de aceste doua controale nou introduse:

In afară de proprietăţile comune tuturor controalelor (BackColor, Text, Font, etc) la CheckBox ne interesează proprietatea “Checked” care ne spune starea controlului: true/false. In funcţie de această stare, în câmpul "tbView" se inserează informaţii referitoare la cazarea în cămin şi la bursa studentului.

3.3.4 Controlul RadioButton (buton radio)

Acest control permite selecţia unei opţiuni din mai multe posibile. Orice selecţie a unei opţiuni le exclude automat pe celelalte. Controalele de tip radio sunt grupate într-un cadru ce defineşte starea de excludere reciprocă a butoanelor incluse.

Vom continua proiectul cu adăugarea a trei butoane radio care să specifice tipul bursei primita de student: bursă de studii, bursă socială, bursă de performanţă.

Version no.: 2 Page 47 of 82

Page 48: Programare Visual Studio .NET Versiunea 2 Nov 2011

Cele 3 butoane radio sunt incluse intr-un GroupBox cu textul “Tip bursa”. Acest grup este initial invizibil până când se ajunge la un student care primeşte bursă. Când se activează CheckBox-ul chBurse, grupul radio trebuie sa devină vizibil pentru ca utilizatorul să poată selecta tipul de bursă primit de studentul în cauză. In acest scop se adaugă un event-handler pe evenimentul chBurse_CheckedChanged care să rezolve acest task:

Iar în funcţia de vizualizare se adaugă cod care să trateze cazul celor trei butoane radio:

Recapitulare: s-au prezentat cele două controale ce pot lua valori de tip True/False: CheckBox si RadioButton. RadioButton-ul permite o singură selecţie dintr-un grup de opţiuni, în timp ce CheckBox-ul lucrează independent de prezenţa altor controale. Proprietatea de bază a acestor controale este "Checked" care returnează True/False în funcţie de starea butonului.

Version no.: 2 Page 48 of 82

Page 49: Programare Visual Studio .NET Versiunea 2 Nov 2011

3.3.5 Controlul ComboBox (listă ascunsă)

ComboBox-ul reprezintă o listă de elemente din care utilizatorul poate să-şi selecteze linia care-l interesează. Este utilă în cazul când utilizatorul trebuie să aleagă o opţiune din mai multe posibile, similar cu RadioButton-ul, dar ComboBox-ul ocupă mai puţin spaţiu la un număr mare de opţiuni. In plus, ComboBox-ul permite adăugarea, ştergerea unor elemente fără să afecteze design-ul formei.

Vom transforma TextBoxul “tbCity” într-un ComboBox (cbCity) în care se va insera localităţile din care provin studenţii. Astfel, la introducerea unui student, utilizatorul primeşte o listă de localităţi din care selectează localitatea de domiciliu a studentului. Avantajul faţă de TextBox este dat de faptul că nu mai trebuie introdus acelaşi nume de localitate de mai multe ori (cu posibilitatea de a tasta greşit numele localităţii), odată introdusă o localitate, ea va fi gasită în lista şi selectată.

Pentru început populăm acest ComboBox cu câteva localităţi din judeţ: în fereastra StudentView.cs[Design] selectăm controlul cbCity şi completăm proprietatea “Items” (Collection):

In noua versiune a programului, utilizatorul face click pe ComboBox şi se desfăşoară lista cu localităţile inserate până în acel moment:

Afişarea localităţii selectată din ComboBox se face prin apelul proprietatii “Text” :

tbView.Text += string.Format("\r\nLocalitate: {0}", cbCity.Text);

Dar ce facem dacă apare un student dintr-o localitate care nu este în listă? In acest caz, utilizatorul poate scrie în ComboBox noua localitate, iar aceasta va apărea în proprietatea “Text” a controlului. Totuşi e destul de incomod să tot introducem aceeaşi localitate în ComboBox ori de câte ori mai găsim un student din localitatea nou introdusă. E mai simplu să inserăm localitatea în listă ca s-o putem utiliza mai târziu. Pentru aceasta, definim două butoane în forma (bAdd, bDel) care să ne permită adăugarea, respectiv ştergerea de localităţi din listă:

Version no.: 2 Page 49 of 82

Page 50: Programare Visual Studio .NET Versiunea 2 Nov 2011

Nu putem adăuga o localitate în listă dacă ea există deja. De aceea, înainte de adăugare, se parcurge toată lista de localităţi şi se verifică ca noua localitate să nu existe în lista. Verificarea nu trebuie să ţină cont de litere mari sau mici (case sensitive), dacă există "Iasi" în listă şi noi vrem să adăugăm "IASI", funcţia de verificare trebuie sa dea mesaj de eroare. Funcţia "ToUpper()" aduce toate caracterele la litere majuscule şi anulează astfel diferenţa dintre "Iasi" şi "IASI" sau "IAsi", etc.

Stergerea unei localităţi din listă se face simplu: apelăm funcţia Remove din colecţia Items conţinută de listă:

Recapitulare: s-a prezentat controlul ComboBox ce este o listă din care putem selecta un anumit articol sau putem

adăuga altele noi. Lista este formată dintr-o colecţie de obiecte numită "Items" ce păstrează informaţii despre datele stocate în listă, numărul lor, etc; s-au prezentat metode pentru adăugarea unui item sau ştergerea sa din listă

Version no.: 2 Page 50 of 82

Page 51: Programare Visual Studio .NET Versiunea 2 Nov 2011

4 Salvarea datelor de lucru

4.1 Salvarea datelor în fişier

Aplicaţia StudentView lucrează foarte bine atât timp cât programul rulează, toate localităţile adăugate în listă în timpul rulării programului pot fi vizualizate şi refolosite. Dar daca am închis programul şi revenim mâine la servici, observăm ca lista de localităţi este iaraşi la valoarea iniţială, setată de noi în fereastra de Design. Toate localităţile adăugate de utilizator pe parcursul rulării programului s-au pierdut. Acest lucru este normal, pentru că lista este un simplu obiect din formă care se instanţiază în memorie la încărcarea formei, adăugam în el localităţi ce sunt salvate în memoria alocată acestui obiect, dar la închiderea formei, toate obiectele dispar cu tot ce s-a încărcat în ele. Rămân doar localităţile introduse de noi prin proprietatea “Items” şi care sunt “hardcodate” în funcţia “InitializeComponent()”.

Singura soluţie de memorare a localităţilor nou introduse este de a salva conţinutul listei într-un fişier pe disc la închiderea programului şi citirea acestuia de pe disc la încărcarea formei.Scrierea/citirea informaţiilor pe disc reprezintă un task relativ dificil, dar VS pune la dispoziţia programatorilor clasa System.IO.File ce include metode puternice şi uşor de utilizat pentru lucrul cu dispozitivele de stocare a informaţiei.

Crearea unui fişier text este foarte simplă cu ajutorul acestei clase şi include următorii paşi: deschiderea fişierului (functia File.CreateText) în care se salvează datele (eventual crearea

acestuia dacă nu există); scrierea datelor în fişier (funcţia WriteLine); închiderea fişierului (Close ).

Vom adăuga un handler la evenimentul "FormClosing" care să salveze în fişierul “localitati.txt” de pe directorul curent toate localităţile inserate în lista “cbCity”:

Observaţii: denumirea fişierului s-a introdus ca un string constant (const string FILE_NAME = "localitati.txt";),

const înseamnă că acest string nu poate fi modificat în decursul programului; scrierea datelor în fişier se face cu obiectul "StreamWriter" returnat de funcţia

"File.CreateText(FILE_NAME);"

Version no.: 2 Page 51 of 82

Page 52: Programare Visual Studio .NET Versiunea 2 Nov 2011

se parcurge toată lista de localităţi şi fiecare localitate va ocupa un rând din fişierul text scris pe disc (sw.WriteLine(cityItem.ToString());). Nu întotdeauna liniile din ComboBox conţin stringuri, de aceea trebuie convertit la string fiecare item din listă. evident, orice acces la un fişier pe disc trebuie facut într-o schemă try...catch...finally care se încheie

cu instrucţiunea de închidere a fişierului dacă acesta este deschis (în caz contrar fişierul nu mai poate fi accesat de alte aplicaţii sau de aceeaşi aplicaţie în alta instanţă):

if (sw != null) { sw.Close(); }

4.1.1 Citirea datelor din fisier

La fel de bine trebuie creat un handler care să citească datele din fişier şi să le pună în listă. Se ataşează la evenimentul "Load" a formei "StudentView" următoarea funcţie:

Se creează de data aceasta un obiect de tip "StreamReader " cu care se deschide fişierul şi se citesc liniile din acesta. Lista cbCity este golită în prealabil (cbCity.Items.Clear();), după care se parcurge fişierul (while ((line = sr.ReadLine()) != null)) şi fiecare linie din fişier va forma un item în ComboBox (cbCity.Items.Add(line);).

Recapitulare: informaţiile adăugate într-un ComboBox se pierd la ieşirea din program, de aceea s-au dezvoltat

funcţii care să salveze şi să citească aceste informaţii din fişiere. Cu această ocazie s-au prezentat clasele File , StreamReader şi StreamWriter;

Version no.: 2 Page 52 of 82

Page 53: Programare Visual Studio .NET Versiunea 2 Nov 2011

4.2 Serializare

Am văzut că avem la dispoziţie clasele "File", "StreamWriter", "StreamReader" cu care să salvăm în fişier şiruri de caractere. Totuşi, dacă vrem să salvăm toate datele specifice unui student aceste clase nu prea ajută. Este mai uşor să salvez aceste date într-o clasă şi apoi să salvez întreaga clasă ca un tot unitar.

In acest scop putem folosi Serializarea, o tehnică ce permite salvarea pe disk a obiectelor complexe care includ atât şiruri de caractere, cât şi metode, componente de diverse tipuri (din care unele pot fi la rândul lor alte obiecte).

4.2.1 Salvarea informaţiilor în clasa Student

Este util ca toate informaţiile ce ţin de un student să fie salvate într-o singur obiect, iar acesta să fie trimis ca referinţă către toate clasele ce prelucrează informaţiile respective.Butonul 'bView" folosit pentru vizualizarea datelor va fi folosit şi pentru salvarea acestora într-un obiect de tipul "Student". Acel obiect va fi mai departe salvat pe disc prin serializare, trimis către o bază de date, etc.

Se declară un obiect de tipul Student în clasa "StudentView", iar acel obiect va fi iniţializat în funcţia "bView_Click" prin apelarea metodei "private void FillStudent()". In acest fel, utilizatorul verifică în ferestra de jos datele studentului şi în acelaşi timp completează şi câmpurile obiectului de tip Student:

4.2.2 Serializarea clasei Student

In acest context ne trebuie o clasă ce primeşte un obiect dintr-o clasă oarecare, îl salvează pe disc, urmând ca la un moment dat, să putem citi obiectul de pe disc şi să-l reconstruim în forma iniţială. Acest proces de salvare/citire a obiectelor pe disc se numeşte serializare/deserializare. VS-ul pune la dispoziţia programatorului clase şi metode care permit memorarea obiectelor în fişiere folosind formatul XML-SOAP (Simple Object Acces Protocol). Clasa "System.Runtime.Serialization.Formatters.Soap.SoapFormatter" conţine toate metodele necesare în serializarea sau deserializarea obiectelor.

Version no.: 2 Page 53 of 82

Page 54: Programare Visual Studio .NET Versiunea 2 Nov 2011

Problema e ca această clasă nu se gaseşte în bibliotecile ataşate implicit de VS, ea trebuie adaugată prin mecanismul: Project/Add Reference... :

Se alege referinţa "System.Runtime.Serialization.Formatters.Soap" şi se dă click pe OK, iar aceasta va apărea în capitolul "References" al proiectului:

In continuare vom adăuga două funcţii care să salveze pe disc obiectul Student din forma curentă, respectiv să citească informaţiile studentului din fişier şi să le afişeze în formă.

Nu orice clasă poate fi serializată, ci doar cele care au atributul [Serialize()] , în caz contrar, la serializare apare eroarea:

Noi nu am declarat clasa "Student" cu acest atribut, deci o facem acum:

[Serializable] public class Student:Person, IStudent

Adăugăm cele două butoane pentru citire/scriere a obiectului Student pe disc şi funcţiile aferente:

Version no.: 2 Page 54 of 82

Page 55: Programare Visual Studio .NET Versiunea 2 Nov 2011

Funcţia de scriere în fişier:

Funcţia de citire din fişier şi afişarea datelor în fereastră:

Version no.: 2 Page 55 of 82

Page 56: Programare Visual Studio .NET Versiunea 2 Nov 2011

Cele două funcţii apelează funcţia privată de afişare a datelor dintr-un obiect Student primit ca parametru:

In caz că obiectul Student este null, atunci funcţia şterge toate câmpurile de afişare, pregătindu-le pentru introducerea unui alt student.

Version no.: 2 Page 56 of 82

Page 57: Programare Visual Studio .NET Versiunea 2 Nov 2011

Dacă totul decurge normal, pe disc în directorul curent se creează un fişier de forma:

în care se păstrează toate datele referitoare la student.

Recapitulare: în acest capitol s-au definit termenii de serializare/deserializare şi s-a adaugat o nouă referinţă la

proiectul în lucru (clasa necesară serializării); nu toate clasele se pot serializa, ci doar cele care au atributul [Serialize()] ataşat. Astfel, clasele

String, ArrayList,.. pot fi serializate direct, dar clasa Student nu are acest atribut. La crearea unei clase noi pe care vrem să o serializăm se adaugă acest atribut în definiţia clasei (inclusiv pentru clasa părinte).

Version no.: 2 Page 57 of 82

Page 58: Programare Visual Studio .NET Versiunea 2 Nov 2011

4.3 Salvarea informaţiilor în baze de date

4.3.1 Ce este o bază de date

Am vazut în capitolul anterior că VS pune la dispoziţia programatorilor instrumente puternice de salvare a informaţiilor de lucru în fişiere. Clasa “File” conţine funcţii de creare şi de acces la fişiere de pe disc, iar prin serializare/deserializare putem salva orice tip de obiect ce are proprietatea [Serializable()].

Totuşi, când avem un volum mare de informaţii ce trebuie gestionate, aceste instrumente devin ineficiente. Singura soluţie viabilă în gestionarea volumelor mari de informaţii rămâne un Sistem de gestionare a bazelor de date (SGBD).

Baza de date este un serviciu ce lucrează independent de programul nostrum, la care noi putem apela să stocăm informaţii diverse în cantităţi foarte mari, după care să cerem aceste informaţii conform unor criterii şi tehnici de filtrare. Dacă luăm ca exemplu activitatea didactică dintr-o unitate de învăţământ, atunci constatăm că ne trebuie o bază de date formată din tabele în care să introducem informaţiile specifice acestei activităţi:

un tabel “studenti”, în care să salvăm pe linii detaliile pentru fiecare student din facultate; tabele pentru grupe, ani şcolari, ani de studiu, specializări, domenii: în care să se salveze

structura organizatorică a facultăţii; tabele pentru disciplinele studiate în facultate cu informaţiile specifice: număr de ore de curs,

număr credite asociate disciplinei, lucrări de laborator sau seminar, etc.; tabele pentru examene, note obţinute de studenţi la examene, sesiunile de examinare, etc. etc…

Tabelele nu sunt independente unele de altele,între ele există relaţii de legătură care să permită obţinerea de informaţii complexe, culese din mai multe tabele legate prin aceste relaţii. De exemplu, tabela “studenti” este legată de tabela “grupe” (fiecare student apartine unei grupe într-un an scolar), mai este legata de tabela “discipline” ( un student studiaza un anumit numar de discipline la un moment dat), de tabela “note” în care să găsim notele acordate studentului respectiv, etc.

Avantajele utilizării bazelor de date în construcţia programelor de gestionare a informaţiilor sunt evidente: Baza de date primeşte o cantitate mare de informaţii pe diverse căi şi de la mai multi utilizatori

simultan, le stochează într-un depozit unic astfel încât, orice informaţie introdusă de un utilizator este vizibilă tuturor. De exemplu, dacă un profesor a introdus în baza de date nota unui student la disciplina asociată, secretara poate calcula media fără să fie nevoită să meargă la profesor ca să ceară notele studenţilor. Cei doi utilizatori lucrează în sedii diferite, cu aplicaţii diferite, dar informaţia este vizibilă tuturor.

Baza de date dispune de mecanisme puternice de extragere a informaţiilor complexe. Pe baza limbajului SQL (Structured Query Language) utilizatorul scrie instrucţiuni de selecţie a datelor din mai multe tabele simultan şi conform unor criterii de filtrare performante. Astfel, pot cere de exemplu bazei de date (cu ajutorul unei instrucţiuni SQL), să-mi dea studentul cu media cea mai mare din grupa 6304 la disciplina “Masurari Electrice” în sesiunea de vară 2007.

Acceptă deschiderea mai multor sesiuni de lucru simultan, astfel încât pot lucra diverşi utilizatori (unii introduc date, alţii vizualizează, alţii modifică), fără să se blocheze unul pe celălalt.

Baza de date este independentă faţă de aplicaţiile care o utilizează, unii utilizatori introduc date printr-o interfaţă realizată în Oracle Forms, alţii printr-un program dezvoltat în VS.NET, alţii vizualizează date prin Oracle Reports, sau Crystal Reports, sau pur şi simplu, printr-un editor SQL. Fiecare işi deschide o sesiune de lucru specifică prin care are acces la toate informaţiile din baza de date.

Sunt mai multe servere de baze de date (SGBD) pe piaţa de software: Oracle, SQL Server, MySql, SQLite, Acces, FoxPro, etc. Utilizatorul alege SGBD-ul care satisface cel mai bine compromisul între performanţă şi preţ. In continuare vom lucra cu o bază de date SQLExpress, fiind o bază cu performanţe deosebite la un preţ de cost rezonabil.

Version no.: 2 Page 58 of 82

Page 59: Programare Visual Studio .NET Versiunea 2 Nov 2011

4.3.2 Instalarea bazei de date

Ne conectăm la site-ul Microsoft de unde downloadăm kit de instalare a serverului SQLExpress ce este distribuit gratuit de firma respectivă (atenţie: trebuie să aveţi VS 2008 SP1 instalat în calculator).

https://www.microsoft.com/betaexperience/pd/SQLEXPDB32/enus/

Instalăm serverul SQLExpress ce va funcţiona sub formă de serviciu Windows, îl putem verifica deschizând fereastra de servicii din ControlPanel/AdministrativeTools:

Deschidem programul "Microsoft SQL Server Management Studio" ce vine automat cu acest SQLExpress şi în cadrul acestui program creăm o nouă bază de date numită "Student" (alegem folderul de instalare "D:\StudentDatabase" pe care-l creăm în prealabil).

In urma acestei operaţii se vor crea în folderul respectiv două fişiere ce vor conţine informaţiile din baza de date: "Student.mdf" şi "Student_log.ldf".

4.3.3 Crearea tabelelor

Click dreapta pe folderul "Tables" din baza Student şi se alege opţiunea "New Table":

Version no.: 2 Page 59 of 82

Page 60: Programare Visual Studio .NET Versiunea 2 Nov 2011

Se creează tabela "Ani_Universitari" cu următoarele coloane:o Pk_an_universitar: tip "int", primary key, autoincrement (Is identity, Identity increment=1):

o Cod_an_universitar: nchar(20), not null;o Descriere_an_universitar: nchar(50)

Se creează tabela "Grupe" cu următoarele coloane:o Pk_grupa: tip "int", primary key, autoincrement (Is identity, Identity increment=1):o Cod_grupa: nchar(20), not null;o Descriere_grupa: nchar(50)

Tabela "Studenti" cu următoarele coloane:o Pk_student: tip "int", primary key, autoincrement (Is identity, Identity increment=1):o Nume_student: nchar(50), not null;o Prenume_student: nchar(50), not null;o Adresa: nchar(50);

Tabela "Studenti_Grupe": este tabela de legătură între studenţi şi grupe. In fiecare an universitar, fiecare student se înscrie la câte o grupă (grupa din anul superior dacă este promovat, sau aceeaşi grupă dacă rămâne repetent). Această tabelă conţine informaţiile despre student specific unui an universitar: la ce grupă este, număr de credite, medie obţinută.

Coloanele sunt următoarele:o Pk_student_grupa: tip "int", primary key, autoincrement (Is identity, Identity increment=1):o Pk_student: face legătura la pk_student din tabela studenţi, specifică în mod unic studentul;o Pk_grupa: face legătura la pk_grupa din tabela grupe, specifică în mod unic grupa;o Pk_an_universitar: specifică în mod unic anul universitar în curs;o Credite: tipul float, memorează numărul de credite obţinute de student în anul universitar

current

Version no.: 2 Page 60 of 82

Page 61: Programare Visual Studio .NET Versiunea 2 Nov 2011

o Medie: tipul float, reprezintă media studentului pe acel an universitar Se salvează tabelele şi se dechide fereastra "Diagrams". In această fereastră utilizatorul poate

define legături de tip "foreign key" între tabele. Aceste legături verifică ca nu cumva să se insereze o linie într-o tabelă copil ce nu corespunde unei linii din tabela părinte. De exemplu, nu putem introduce în tabela "Studenti_grupe" o valoare "pk_student" ce nu există în tabela Studenti.

Se definesc următoarele relaţii:o Fk_studenti_grupe_studenti: leagă pk_student dintre tabelele Studenti şi Studenti_Grupe.

Click dreapta pe tabela Studenti_grupe şi se alege opţiunea "Relationships".In cadrul acestei relaţii se setează numele, precum şi coloanele ce trebuie să corespundă intre tabela Student şi tabela curentă.

o Fk_studenti_grupe_grupe: leagă pk_grupa dintre tabelele Grupe şi Studenti_Grupeo Fk_studenti_grupe_ani_univ: leagă pk_an_universitar dintre tabelele Ani_Universitari şi

Studenti_Grupe

In final, diagrama bazei de date "Student" ar trebui să arate conform figurii de mai jos:

Se introduc câteva linii de lucru în fiecare tabel pentru testarea funcţionalităţii:

insert into Ani_universitari(cod_an_universitar, descriere_an_universitar)values('2011-2012','anul 2011-2012');

Version no.: 2 Page 61 of 82

Page 62: Programare Visual Studio .NET Versiunea 2 Nov 2011

insert into Ani_universitari(cod_an_universitar, descriere_an_universitar)values('2012-2013','anul 2012-2013');

insert into Studenti(nume_student, prenume_student, adresa)values('Nume1', 'Prenume1','Iasi');insert into Studenti(nume_student, prenume_student, adresa)values('Nume2', 'Prenume2','Focsani');insert into Studenti(nume_student, prenume_student, adresa)values('Nume3', 'Prenume3','Pascani');

insert into Grupe(cod_grupa, descriere_grupa) values('6403','grupa 6403');insert into Grupe(cod_grupa, descriere_grupa) values('6404','grupa 6404');insert into Grupe(cod_grupa, descriere_grupa) values('6408','grupa 6408');

insert into Studenti_Grupe(pk_student, pk_grupa, pk_an_universitar, Credite, Medie) values(11,1,1,45,9.40);insert into Studenti_Grupe(pk_student, pk_grupa, pk_an_universitar, Credite, Medie) values(12,1,1,30,8.15);

4.3.4 Salvarea obiectului "Student" în baza de date

4.3.4.1 Tehnologia ADO.NET

Un capitol important în cadrul platformei VS.NET este dat de tehnologia ADO (ActiveX Data Objects) de gestionare a bazelor de date. ADO.NET reprezintă o mulţime de biblioteci de programe ce permit interacţiunea cu sistemele de stocare a datelor (baze de date, fisiere XML, Excel, etc.).

Arhitectura ADO.NET include trei componente principale:o Baza de date: este suportul în care se stochează dateleo Data Provider (Furnizorul de date) : reprezintă colecţia de obiecte prin care ne conectam la

baza de date şi operăm modificari în ea; o SQLCommand-ul: obiect prin care se execută instrucţiuni SQL asupra bazei de date

Paranteză: SQL este un limbaj standard prin care putem comunica cu orice bază de date. El defineşte un set de instrucţiuni pe care le inţelege şi le execută baza de date. Instrucţiunile sunt scrise într-un editor SQL ce stabileşte mai intâi o conexiune cu baza (folosind cont si parola) şi apoi trimite comenzile către aceasta. Instrucţiunile pot fi pentru vizualizarea şi modificarea datelor din tabelele bazei de date (select , update, insert,…), sau pentru modificarea structurii bazei (create table , drop, alter …).Tot prin instructiuni SQL comunică şi platforma .NET cu baza de date, dar de data aceasta, aceste instrucţiuni sunt scrise si trimise prin intermediul controalelor puse la dispoziţie de libraria ADO.NET.

Există mai multe sisteme de stocare a datelor pe piaţă, fiecare folosind un protocol diferit de comunicaţie, de aceea ADO.NET dispune de cate un DataProvider pentru fiecare tip de bază de date:

o ODBC DataProvider: pentru baze de date mai vechi, ce folosesc protocolul ODBCo OleDB DataProvider: pentru Acces, Excel,..;o Oracle DataProvider: pentru baze de date Oracle;o SQL DataProvider: pentru Microsoft SQLServer;

In acest capitol vom adăuga codul sursă necesar pentru salvarea obiectului "Student" în baza de date. Sunt necesare următoarele etape:

Stabilirea conexiunii la baza de date Instanţierea obiectelor de lucru cu baza de date: SQLConnection şi SQLCommand

Version no.: 2 Page 62 of 82

Page 63: Programare Visual Studio .NET Versiunea 2 Nov 2011

Definirea comenzii SQL de inserare a unuio student în tabela "Studenti" Executarea comenzii SQL Inchiderea conexiunii la baza de date

4.3.4.2 Stabilirea conexiunii la baza de date

Se adaugă în soluţia "OOP Course" un nou proiect de tip "Class Library" numit "DataStore". Class Library că acest proiect este destinat de a fi utilizat de un alt proiect, nu conţine clasa "Program", deci nu poate fi lansat în execuţie de sine stătător.

Se adaugă la referinţele acestui proiect proiectul "OOP Fundamentals" în scopul utilizării clasei Student deja definită acolo.

Click dreapta pe proiectul nou adăugat şi se selctează Properties/Settings. Se defineşte un string de conectare la baza de date "Student":

o Alegem numele noului string de conectare ca fiind"ConnString", tipul este "Connection string", iar la value facem click pe butonul "…".

o Alegem "Data Source" de tipul "Microsoft SQL Server (SQLClient)o Alegem "Use Windows Authentification": utilizatorul deja logat pe computerul respective are

acces direct la baza de dateo Din cadrul bazelor de date definite pe acest server alegem baza noastră "Student"o Click pe "Test Connection" pentru a verifica dacă conexiunea funcţionează

In final se obţine un nou string de conectare având valoarea "Data Source=WKS-LUC\SQLEXPRESS;Initial Catalog=Student;Integrated Security=True" (bine, Data Source depinde de numele calculatorului pe care este instalată baza de date).

Version no.: 2 Page 63 of 82

Page 64: Programare Visual Studio .NET Versiunea 2 Nov 2011

4.3.4.3 Funcţia de salvare a obiectului "Student" în baza de date

In cadrul proiectului "DataStore" se adaugă o clasa "DS_Student" ce conţine o funcţie statică care primeşte ca parametru un obiect din clasa "Student" şi salvează în baza de date acel obiect.Codul funcţiei este dat în continuare:

Funcţia conţine următoarele componente: Stringul "SqlCommandInsert" ce specifică fraza SQL de inserare a unui student în bază. Fraza

include şi anumiţi parametri (@surname,@name,@address) prin prin intermediul cărora se transmit informaţiile pentru fiecare coloană din tabel.

Se creează un obiect de tip "SqlConnection" prin care se realizează conexiunea la baza de date. Acest obiect primeşte ca parametru stringul de conectare pe care-l regăsim în proprietăţile proiectului "DataStore" (Properties.Settings.Default.ConnString) creat la punctul anterior.

Se intră în secţiunea try-catch (orice acces la o bază de date trebuie pusă în try-catch deoarece întotdeauna este posibil ca baza de date să dea erori: serverul este oprit, sau valorile inserate nu respectă o anumită constrângere definită în bază, etc) unde se creează obiectul SqlCommand , se ataşează acestuia comanda SQL ce trebuie executată şi se dau valori parametrilor.

In final se dechide conexiunea la bază, şi se execută comanda SQL: cmd.ExecuteNonQuery(). Obligatoriu trebuie inserată clauza Finally unde se închide conexiunea, indiferent dacă a fost sau

nu eroare în adăugarea studentului.

Revenim la proiectul "StudentView" unde adăugam un nou buton pentru salvarea datelor în baza de date. Funcţia ataşată trebuie doar să apeleze metoda statică DS_Student.AddStudent(currStudent) .

Version no.: 2 Page 64 of 82

Page 65: Programare Visual Studio .NET Versiunea 2 Nov 2011

Observăm că apelul se face tot într-o construcţie try-catch deoarece trebuie prinse erorile aruncate de funcţia apelată. Funcţia "DS_Student.AddStudent" prinde excepţiile, dar nu le afişează pentru că nu are cum. Ne amintim că proiectul "DataStore" este de tip "ClassLibrary", deci nu are ataşată o interfaţă grafică. Controlul "MessageBox" nici nu este disponibil în mod implicit în acest tip de proiect. Aceste proiecte lucrează în background, ele doar execută sarcini trimise din proiectele superioare, gen "StudentView". Orice eroare produsă în aceste procese de background trebuie trimisă mai sus, până ajunge la proiectul de top unde se afişează mesajul de eroare.

4.3.5 Afisarea dinamică a mesajelor. Controalele StatusStrip şi Timer

Orice tranzacţie asupra bazei de date trebuie semnalizată utilizatorului, astfel încât, acesta să aiba un feed-back asupra acţiunilor sale (în funcţia "bSaveDB_Click" am inserat două MessageBox-uri prin care afişăm rezultatul acţiunii: studentul a fost inserat cu succes, sau respectiv cu eroare).

Dar folosirea abuzivă a casetelor de dialog de tipul "MessageBox" poate fi deranjantă pentru un utilizator ce introduce un volum mare de informaţii în bază. Utilizatorul vrea sa introducă cât mai rapid datele, să nu fie agresat cu tot felul de mesaje de tipul "Adaugarea s-a efectuat cu succes" la care el să fie obligat să acţioneze butonul de închidere a dialogului.

Este mai comod pentru el dacă , după fiecare acţiune a sa , să apară în partea de jos a formei un mesaj de la program despre modul de finalizare a acţiunii, mesajul să apară pentru 2,3 secunde şi apoi să dispară singur. In felul acesta, utilizatorul se poate concentra asupra muncii de introducere a datelor şi în acelaşi timp, are şi informaţia despre rezultatul acţiunii sale.

VS-ul deţine un control specializat pentru afişarea mesajelor numit StatusStrip. Acesta se prezintă ca o bară de stare ce poate fi ataşată la una din marginile ferestrei (implicit se afişeaza în partea de jos) şi conţine diverse alte controale de afişare a informaţiei : StatusLabel, ProgressBar, DropDownButton, SplitButton.

In acelasi timp ne trebuie un control care să măsoare timpul de afişare a mesajului, iar când acesta s-a terminat, să anuleze mesajul. Pentru măsurarea timpului folosim controlul "Timer", ce este un cronometru care se incrementează la fiecare milisecundă. Are două proprietăţi de lucru şi un eveniment asociat:

Enabled: True/False : se porneşte sau se opreşte cronometrul Interval: numărul de milisecunde la care cronometrul trebuie să dea alarma Tick: este evenimentul lansat de Timer când s-a expirat timpul setat de proprietatea Interval.

Se deschide fereastra ToolBox şi se alege controlul TimerControlul prin operaţia drag&drop. Se setează numele "timerStatusStrip" şi se asociază funcţia "timerStatusStrip_Tick" la evenimentul "Tick" trimis de acesta. Timer-ul nu apare în interfaţa cu clientul deoarece el nu este un control vizual, el este doar un obiect în spatele scenei ce măsoară timpul şi emite evenimentul "Tick" la fiecare interval de timp stabilit. Totuşi, el se vede în faza de dezvoltare a formei în partea de jos a ferestrei:

Version no.: 2 Page 65 of 82

Page 66: Programare Visual Studio .NET Versiunea 2 Nov 2011

.

Se adaugă de asemenea la forma StudentView un control de tip StatusStrip numit "statusStripView" la care se adaugă un control de tip label numit "statusStripLabel" folosit pentru afişarea mesajelor.

In continuare vom scrie o funcţie care primeşte ca parametri mesajul ce trebuie afişat şi timpul de afişare, iar funcţia va afişa mesajul în StatusStrip.

La fiecare apel al funcţiei se pune mesajul în "statusStripLabel" şi se porneşte ceasul cu intervalul stabilit. La terminarea intervalului se şterge mesajul şi se opreşte ceasul.

Recapitulare: am studiat în acest capitol o modalitate de a salva informaţiile din clasa StudentView într-o bază de date:

Am definit baza de date Am creat tabelele de stocare a informaţiei Am creat clasa "DataStore" pentru accesul la baza de date. In această clasă am definit obiectele

SQLConnection şi SQLCommand. Am arătat cum se creează stringul de conectare la baza de date şi cum se stochează în setările

proiectului Am creat un buton în StudentView pentru salvarea datelor în bază specificând modul de

transmitere şi afişare a erorilor Am introdus controlul StatusStrip pentru afişarea mesajelor de lucru

Version no.: 2 Page 66 of 82

Page 67: Programare Visual Studio .NET Versiunea 2 Nov 2011

5 Aplicaţia "E-Student"

In acest capitol vom finaliza aplicaţia construită pe parcurs, prin adăugarea unor noi facilităţi absolut necesare într-o aplicaţie de gestionare a informaţiilor referitoare la studenţi:

Adăugarea, modificarea sau ştergerea unui student din baza de date Afişarea studenţilor pe grupe şi ani universitari în tabele de tip DataGrid Funcţia de căutare a unui student ...

Pe parcurs vom introduce noi clase sau controale necesare în implementare aacestor funcţionalităţi: colecţii de obiecte, controale de afişare tabelară, progress bar, etc.

5.1 Colecţii de obiecte

Forma noastră de până acum merge foarte bine dacă lucrăm cu un singur student. Dar practica este complet alta, în realitate trebuie sa introducem şi să salvăm date despre o mulţime de studenţi. Avem deci nevoie de o clasă în care să putem insera mai multe obiecte de tip student şi clasa să-mi ofere posibilităţi de inserare, ştergere, etc.

Un prim pas în gruparea obiectelor este tabelul utilizat şi în programarea clasică C (de exemplu int[] values = {1, 2, 3, 4};), dar acesta este o simplă colecţie de obiecte de acelaşi tip care se pot accesa printr-un index. Adăugarea/eliminarea unui obiect dintr-un tabel este o operaţie greoaie şi trebuie făcută de programatorul care utilizează tabelul.

In schimb, se poate utiliza clasa "ArrayList" din libraria "System.Collection" ce permite adăugarea/ştergerea în mod dinamic a elementelor, obiectul de tip ArrayList se redimensionează automat la modificările aduse în timpul execuţiei programului. In plus în colecţie se pot insera obiecte de tipuri diferite.Metodele de lucru ale clasei "ArrayList" sunt următoarele:

Add: adaugă un obiect în listă; Remove: şterge din listă obiectul primit ca parametru RemoveAt: şterge din listă obiectul de un indice dat. Find: caută un obiect dintr-o listă pe baza unei funcţii de comparare date

Accesul la obiectele din listă se face prin indici, primul element din listă având indicele zero. Colecţiile de obiecte suportă de asemenea instrucţiunea "foreach" prin care se poate baleia toate elementele din listă.

O altă clasă din librărie se referă la o colecţie de obiecte de acelaşi tip, dar tipul este definit in timpul creării colecţiei, sunt aşa numitele "colecţii generice" (System.Collections.Generic.List<T>). Putem crea cu ajutorul acestei clase colecţii de orice tip (string, Student, etc):

Să facem un mic exemplu în clasa "Program" din proiectul tip consolă pentru a verifica aceste funcţionalităţi:

S-a definit o colecţie de tip "Student" numită "myStudentList" la care s-au adăugat 3 studenţi. Prin instrucţiunea "foreach" s-au afişat toţi studenţii din listă, iar prin apelarea funcţiei "Find" s-a căutat studentul "Stud1" în listă. Funcţia "Find" trebuie să primească un delegate (un pointer la o funcţie) care ştie cum să compare două obiecte din clasa "Student" (cazul de faţă s-a căutat un student după nume).

Version no.: 2 Page 67 of 82

Page 68: Programare Visual Studio .NET Versiunea 2 Nov 2011

Acest cod de program produce următoarea ieşire la consolă:

5.2 Citirea listelor de studenţi din baza de date

In continuare vom adăuga în clasa "DS_Student" o nouă funcţie ce primeşte ca parametri anul universitar, precum şi numărul unei grupe şi returnează lista cu toţi studenţii din acea grupă înscrişi în acel an universitar.

Sunt folosite aceleaşi obiecte descrise în funcţia "AddStudent". In plus s-a definit un nou obiect de tip "SqlDataReader" în care s-au salvat toate informaţiile aduse de fraza "select" din baza de date. Acest obiect conţine funcţia "Read" prin care se baleiază toate liniile colecţiei, iar pe fiecare linie se accesează coloanele aduse de fraza "select" prin intermediul construcţiei "reader["nume_coloana"]".Pentru fiecare linie din colecţia din reader se creează un obiect de tip "Student" (currStudent) ce se adaugă la lista "studentList" (studentList.Add(currStudent);).

Version no.: 2 Page 68 of 82

Page 69: Programare Visual Studio .NET Versiunea 2 Nov 2011

5.3 Forma "E-Student"

Dorim să construim o formă care să vizualizeze studenţii sub forma unui grid, având posibilitatea să modificăm datele unui student, să ştergem sau să adăugăm un nou student. Forma va afişa studenţii selectaţi după ani universitari şi grupe:

5.3.1 Afişarea anilor universitari

Anii universitari vor fi afişaţi într-un ComboBox, iar selecţia unui anumit an va conduce la refresh-area celorlalte controale. Toată informaţia afişată în formă depinde de anul universitar selectat.Similar cu controlul "cbClass", se creează un alt ComboBox ce va fi populat cu informaţiile citite din tabela "ani_universitari" din baza de date.

Trebuie parcurşi următorii paşi: Definirea clasei "DS_Year" în proiectul "DataStore", clasă ce conţine proprietăţi pentru cele trei

informaţii ce descriu un an universitar: PK, cod, descriere.

Construirea funcţiei "List<DS_Year> GetYearList()" ce returnează o listă cu toţi anii găsiţi în baza de date. Funcţia se defineşte chiar în interiorul clasei "DS_Year" şi este similară cu funcţia "GetStudentList" prezentată mai sus.

Version no.: 2 Page 69 of 82

Page 70: Programare Visual Studio .NET Versiunea 2 Nov 2011

Ataşarea listei la controlul ComboBox ("cbYear"). Controlul ComboBox permite definirea liniilor direct în colecţia "Items", dar de asemenea, pot să-l populez cu date în mod dinamic prin ataşarea lui la o listă citită din bază (DataSource = yearList):

ComboBox-ul are două componente:- "DisplayMember" este proprietatea care se afişează sub formă de text;- "ValueMember" este valoarea pe care o returnează controlul.

In cazul nostru, afişăm codul grupei, dar valoarea de care avem nevoie este PK-ul anului universitar. Cu acest PK putem găsi în continuare grupele ce au studenţi în acest an universitar şi mai departe studenţii ce aparţin grupei selectate în celălalt ComboBox.

5.3.2 Afişarea grupelor

Pentru grupe inserăm un ComboBox pe care-l ataşăm de lista grupelor găsite în baza de date. Următorii paşi trebuie parcurşi:

Definirea clasei "DS_Class" în proiectul "DataStore", clasă ce conţine proprietăţi pentru cele trei informaţii ce descriu o grupă: PK, cod, descriere.

Version no.: 2 Page 70 of 82

Page 71: Programare Visual Studio .NET Versiunea 2 Nov 2011

Construirea funcţiei "List<DS_Class> GetClassList(int pkYear)" ce returnează o listă cu toate grupele găsite în bază pentru un an universitar dat.

Ataşarea listei la controlul ComboBox ("cbClass").

5.3.3 Popularea automată a listelor de tip ComboBox

Version no.: 2 Page 71 of 82

Page 72: Programare Visual Studio .NET Versiunea 2 Nov 2011

Doresc ca atunci când selectez un an universitar, lista grupelor afişate să se modifice automat, afişând doar grupele ce au studenţi în acel an universitar. De asemenea, pentru fiecare grupă selectată să se afişeze automat studenţii din acea grupă şi acel an universiar.

In acest scop, interceptăm evenimentul "SelectedIndexChanged" al fiecărui ComboBox:

Iar ComboBox-ul de la care pornesc toate afişările îl populez chiar în constructorul clasei:

5.3.4 Afişarea studenţilor. Controlul DatGridView

Studenţii sunt mult mai mulţi, nu pot fi afişaţi folosind ComboBox sau TextBox. Cel mai indicat control pentru afişarea datelor complexe este "DataGridView". Acest control este un grid cu linii şi coloane ce se poate ataşa la o listă de obiecte (în cazul nostru, o listă de obiecte "Student". Proprietăţile obiectului "Student" vor deveni coloane în data grid, iar informaţiile dintr-un obiecte vor forma o linie in grid.

Este un control foarte complex, dar uşor de utilizat pentru afişarea datelor. Pune la dispoziţie o mulţime de proprietăţi prin care putem modifica aspectul gridului, dar şi o mulţime de evenimente prin care putem răspunde la acţiunile utilizatorului.

Următoarele acţiuni trebuie îndeplinite pentru a face un grid funcţional: Instanţierea controlului "DataGridView": se aduce un datagrid din toolbox şi se poziţionează în

formă (numele obiectului va fi "dgStudent"). Automat, funcţia "InitializeComponents" va include toate liniile de cod necesare iniţializării acestui control complex.

Ataşarea gridului la lista de obiecte ce trebuie afişate: se alege proprietatea "DataSource" şi se merge pe opţiunea "Add Project Data Source...":

Noua sursă de date va fi de tip "Object" şi se alege din namespace-ul "OOP_Fundamentals" ca fiind clasa "Student".

Version no.: 2 Page 72 of 82

Page 73: Programare Visual Studio .NET Versiunea 2 Nov 2011

Observaţie: dacă nu apare acest namespace, înseamnă că nu aţi adăugat proiectul "OOP_Fundamentals" la capitolul "References". Adăugaţi proiectul împreună cu celelalte: "DataSource" şi "StudentView". Noul nostru proiect numit "E-Student" se situează în topul tuturor, adică va utiliza clasele definite în proiectele inferioare. Nu se poate face referire decât într-un sens, dacă proiectul 1 face referire la proiectul 2, atunci proiectul 2 nu va putea adăuga la referinţe proiectul 1, ar ieşi "Referinţă Circulară".

Editarea coloanelor: în acest moment, toate proprietăţile publice ale clasei "Student" (inclusiv cele moştenite de la clasa părinte "Person") vor fi ataşate sub formă de coloane la grid. Dar nu toate aceste coloane sunt utile, limităm aceste coloane la strictul necesar.

Se selectează link-ul "Edit Columns" din proprietăţile gridului şi se intră în fereastra de editare a coloanelor. Aici avem posibilitatea să definim pentru fiecare coloană modul de afişare, textul de header, sau eventual să adăugăm coloane noi, diferite de cele din clasa Student (de exemplu, vom adăuga coloane ce conţin butoane pentru ştergerea studentului din baza de date, modificarea lui, sau adăugarea unui student nou).

Vom lăsa doar coloanele utile şi se adaugă coloanele de tip "DataGridViewLinkColumn": Del, Update şi Add.

Version no.: 2 Page 73 of 82

Page 74: Programare Visual Studio .NET Versiunea 2 Nov 2011

Observaţie: proprietăţile PKStudClass şi PKStud trebuie adăugate în clasa "Student". Ele vor conţine PK-urile din tabelele "grupe_studenti" şi "studenti" din baza de date pentru studentul respectiv.

Afişarea datelor: în cadrul constructorului formei se apelează funcţia "FillCbYear()". Mergând în cascadă pe evenimentele "SelectedIndexChanged" se vor popula toate controalele cu datele specifice anului universitar curent. Ultimul eveniment se produce la ComboBox-ul "cbClass" ce va apela funcţia de populare a gridului "FillDgStudent()":

Se preiau anul universitar şi grupa selectate în ComboBox-uri şi se determină lista studenţilor înscrişi în acea grupă pentru acel an (List<Student> studList = DS_Student.GetStudentList(pkClass, pkYear)) . Funcţia este implementată în clasa DS_Student şi este similară cu celelalte funcţii de tip "Get..". Diferenţele între aceste funcţii sunt date de fraza SQL ce trebuie executată:

Lista de studenţi obţinută se ataşează ca DataSource la gridul de afişare.

In sfârşit, avem o formă funcţională, ce afişează studenţii filtraţi după grupe şi ani universitari:

Version no.: 2 Page 74 of 82

Page 75: Programare Visual Studio .NET Versiunea 2 Nov 2011

5.3.5 Interceptarea evenimentelor date de grid

Dacă facem click pe unul din butoanele din grid în acest moment, nu se întâmplă nimic. Deoarece nu am ataşat nici o funcţie la evenimetele trimise de grid. Sunt multe moduri de a rezolva această problemă, dar cel mai simplu ar fi să interceptăm evenimentul "CellClick" al gridului şi pe acel eveniment să rezolvăm toate butoanele.

Evenimentul "CellClick" apare la orice click al utilizatorului într-o celulă de grid, deci inclusiv în celulele cu nume, prenume, etc. Sunt două filtrări ce trebuie realizate pe acest eveniment:

- Eliminarea click-urilor pe alte celule decât cele cu butoane:dgStudent.CurrentCell.GetType() != typeof(DataGridViewLinkCell)- Identificarea butonului pe care s-a facut click:

if ((string)dgStudent.CurrentCell.Value == "Update")

Version no.: 2 Page 75 of 82

Page 76: Programare Visual Studio .NET Versiunea 2 Nov 2011

Butoanele "Update" şi "Add" sunt tratate aproximativ la fel, ambele deschid forma "StudentView", dar în moduri diferite. Butonul de adăugare deschide forma în starea ei normală (forma a fost făcută tocmai pentru a adăuga studenţi în baza de date), pe când butonul de "Update" trebuie să deschidă forma care să afişeze datele studentului selectat. In acest mod, utilizatorul doar modifică anumite date, după care studentul trebuie updatat în baza de date.

Pentru modul de "Update" s-a implementat un nou constructor al clasei "StudentView" ce primeşte ca parametru întregul "pkStudClass" , adică valoarea "pk_student_grupa" din baza de date. Această valoare identifică în mod unic linia din tabela "studenti_grupe" ce identifică la rândul ei în mod unic studentul (pk_student), grupa (pk_grupa) şi anul universitar (pk_an_universitar).

Trebuie adăugată în fiecare clasă (DS_Student, DS_Class, DS_Year) câte o funcţie ce primeşte parametrul pk_student_grupa şi returnează în mod unic studentul, grupă şi anul. Prezentă în continuare numai funcţia din clasa DS_Class, celelalte se construiesc în mod asemănător:

Version no.: 2 Page 76 of 82

Page 77: Programare Visual Studio .NET Versiunea 2 Nov 2011

Noul constructor al formei "StudentView" apelează vechiul constructor pentru iniţializarea controalelor, apoi setează anumite controale în funcţie de studentul primit ca parametru ( ComboBoxul cu grupele trebuie să fie setat pe grupa studentului, butonul "Salvare in baza de date" se transformă în "Update", etc):

Mai sunt necesare câteva modificări pentru a face forma funcţională, noi doar evidenţiem două funcţii ce trebuie modificate: "bSaveDB_Click":

Version no.: 2 Page 77 of 82

Page 78: Programare Visual Studio .NET Versiunea 2 Nov 2011

, respectiv "DS_Student.UpdateStudent(currStudent)":

5.3.6 Inscrierea studenţilor în grupe

La începutul fiecărui an universitar studenţii ce s-au înscris la facultate în primul an trebuie repartizaţi pe grupele din anul unu, respectiv studenţii mai vechi care au absolvit un an de studiu trebuie înscrişi în următorul an de studiu.Vom face o formă specială pentru în scrierea studenţilor în diverse grupe şi pentru diverşi ani de studiu. Forma este similara cu cea precedentă, cu deoasebirea că gridul conţine un singur buton "Inscrie" care va avea ca efect înscrierea studentului din linia curentă în grupa şi anul de studiu selectat.

Version no.: 2 Page 78 of 82

Page 79: Programare Visual Studio .NET Versiunea 2 Nov 2011

Inserăm un buton ajutător "Vezi numai studentii fara grupa" ce va afişa numai studenţii care nu sunt înscrişi în anul universitar curent.

Este foarte important să nu înscriem de două ori un student în acelaşi an universitar. Cea mai sigură soluţie este să adăugăm o constrângere de tip "unique" pe tabela "studenti_grupe" pentru perechea de coloane "pk_student" şi "pk_an_universitar":

alter table studenti_grupe add constraint u_student_an unique(pk_student, pk_an_universitar);

Binenţeles, toate aceste modificări necesită adăugarea de cod nou în proiectele noastre, dar toate aceste modificări pot fi realizate pe baza template-ului prezentat până în acest moment. Ne rezumăm să exemplificăm funcţia de înscriere student din proiectul "DataSource":

Version no.: 2 Page 79 of 82

Page 80: Programare Visual Studio .NET Versiunea 2 Nov 2011

5.3.7 Conectarea aplicaţiei la diverse tipuri de baze de date

Toate funcţiile din proiectul "DataStore" sunt specifice bazei de date Microsoft SqlExpress. Stringul de conectare la bază utilizează drivere native ale bazei de date pentru accesarea informaţiei. De aceea şi obiectele folosite pentru accesarea bazei erau specifice serverului SqlExpress: SqlConnection, SqlCommand.

Dar dacă vrem să ne conectăm la altă bază de date (Oracle, Acces, MySql, SQLite, etc), atunci aceste obiecte nu mai sunt valabile. Oracle pune la dispoziţie drivere proprii în biblioteca "using Oracle.DataAccess.Client" în care defineşte obiecte proprii (OracleConnection, OracleCommand).

Totuşi, există un mod unic de accesa diverse baze de date folosind aceleaşi obiecte. Această tehnologie se numeşte ODBC (Open Database Connectivity) şi are scopul de accesa diversa baze de date în mod similar, independent de baza de date sau de sistemul de operare. In acest caz se lucrează cu obiectele OdbcConnection, OdbcCommand ce au aceeaşi funcţionalitate ca cele similare din SqlExpress.

In figura de mai jos sunt date diverse stringuri de conectare la diverse baze de date folosind tehnologii diferite:

Se observă că pot să pot să mă conectez la aceeaşi bază de date SqlExpress, dar folosind de data aceasta ODBC. La fel de bine, mă conectez la baza Oracle folosind tot ODBC. Avantajul utilizării ODBC este că pot interschimba bazele de date fără să modific codul de program, ci doar stringul de conectare, în rest obiectele rămân aceleaşi.

Nu mai creez aceste obiecte în fiecare clasă, ci fac o singură clasă "DataBaseTools" care va returna obiectul "OdbcCommand" ce este conectat la o bază de date specifică:

Version no.: 2 Page 80 of 82

Page 81: Programare Visual Studio .NET Versiunea 2 Nov 2011

Am folosit directiva preprocesor "#define odbc", iar în funcţie de aceasta, funcţia "GetSqlCommand" returnează un obiect de tip SqlCommand, sau OdbcCommand. Funcţiile din clasele DS_Class, DS_Student, DS_Year nu vor mai crea obiectul SqlCommand, ci-l vor cere de la clasa "DataBaseTools". Nu se specifică tipul obiectului folosit pentru că el se poate modifica în funcţie de directiva #define, de aceea se foloseşte construcţia: "var cmd = DataBaseTools.GetSqlCommand();" .

Totuşi există o diferenţă între obiectele SqlCommand şi OdbcCommand: OdbcCommand nu acceptă parametri în sintaxa "@nume_parametru" ci doar semnul "?", iar parametrii trebuie adăugaţi strict în ordinea utilizării lor în fraza SQL. De aceea, am anulat capitolul "Parameters", punând direct în string valorile acestor parametri:

Recapitulare: în acest capitol s-a construit o formă simplă pentru vizualizarea studenţilor, cu posibilitatea modificării datelor specifice fiecărui student. S-au introdus câteva concepte noi dintre care amintim:

Utilizarea obiectelor de tip "DataGridView" pentru afişarea datelor tabelare. Inserarea de butoane în coloanele gridului pentru efectuarea anumitor sarcini. Crearea dinamică a unei forme şi lansarea ei în execuţie. Prezentarea tehnologiei ODBC pentru conectarea uniformă la diverse baze de date.

Version no.: 2 Page 81 of 82

Page 82: Programare Visual Studio .NET Versiunea 2 Nov 2011

6 Bibliografie

http://www.c-sharpcorner.com

http://msdn.microsoft.com/en-us/library/67ef8sbd(v=vs.80).aspx

http://csharp.net-informations.com/

http://www.csharp-station.com/Tutorial.aspx

Version no.: 2 Page 82 of 82