"Događaji
Ovo je poslednje poglavlje OOP sekcije ove knjige, u kojem opisujemo jednu od najčešće korišćenih OOP tehnika u .NET-u: događaje. Kao i uvek, počećemo od osnovnih stvari i prodiskutovati o tome šta su to zaista događaji. Posle toga, pogledaćemo primere jednostavnih događaja i šta se sve s njima može uraditi. Kada završimo sa tim, videćemo kako da kreiramo i koristimo sopstvene događaje. Dakle, pogledajmo šta su to događaji.
Događaji su slični izuzecima u tome što su aktivirani od strane objekata, a mi možemo obezbediti kod koji reaguje na njih. Međutim, postoje i neke razlike. Jedna od najvažnijih jeste ta da nema ekvivalentne try. . . catch strukture za obradu događaja. Umesto toga, moramo se prijaviti na događaj. Prijavljivanje na događaj podrazumeva pisanje koda koji će se izvršiti kada je događaj aktiviran, u obliku rutine za obradu događaja. Događaj može imati vise ratina za obradu događaja, koje će sve biti pozvane kada je događaj aktiviran. Rutine za obradu događaja mogu biti deo iste klase kojoj pripada objekat koji aktivira događaj, a mogu pripadati i nekim drugim klasama. Rutine za obradu događaja su jednostavne funkcije. Jedina restrikcija za ove funkcije jeste ta da se potpis (tip podatka koji se vraća, i parametri) mora podudarati sa onim što zahteva događaj. Ovaj potpis je deo definicije događaja, određen od strane delegata. Upravo zato što se koristi u događajima, delegat je moćno oružje. To je razlog što smo im posvetili nešto više vremena predavanju o funkcijama. Možda biste hteli ponovo da pročitate ovu sekciju, da se podsetite šta je to delegat i kako se koristi. Sekvenca obrade izgleda ovako: Prvo - aplikacija kreira objekat koji može aktivirati događaj. Na primer, neka je aplikacija zadužena za instant poruke, i objekat koji ona kreira predstavlja vezu sa udaljenim korisnikom. Objekat veze može aktivirati događaj, npr. kada stigne poruka od udaljenog korisnika.
Šta je događaj?
Sledeće - aplikacija se prijavljuje na događaj. Ovo bismo mogli postići definisanjem funkcije koja bi se koristila sa delegat tipom koji je odredio događaj, i koja bi imala parametar referencu na događaj. Rutina za obradu događaja može biti metoda na nekom drugom objektu. Neka to bude, npr. objekat koji predstavlja uređaj za prikaz instant poruke.
1
Kada je događaj aktiviran, aplikacija je obaveštena. Kada stigne instant poruka kroz objekat veze, bide pozvana rutina za obradu događaja na uređaju za prikaz. Pošto koristimo standardnu metodu, objekat koji aktivira događaj može proslediti, kroz parametre, bilo koju relevantnu informaciju praveći tako raznovrsne događaje. U našem slučaju, jedan parametar može biti tekst iz instant poruke. Ovaj tekst može se prikazati pomoću ratine za obradu događaja na uređaju za prikaz.
Korišćenje događaja
U ovoj sekciji pogledaćemo kod ratine za obradu događaja, a zatim ćemo videti kako možemo definisati i koristiti naš sopstveni kod. Obrada događaja Као što smo već rekli, za obradu događaja neophodno je prijaviti događaj, što znači obezbediti ratinu za obradu događaja čiji se potpis podudara sa delegatom koji koristi događaj. Pogledajmo primer koji koristi jednostavan objekat Timer koji aktivira događaj, posle čega će biti pozvana rutina za obradu događaja.
2
Primer obrade događaja
Vežba br. 18.
Napravite novi projekat konzolne aplikacije birajući File | New | Project... iz menija: Izaberite Visual C# Projects direktorijum unutar prozora Project Types:, i tip projekta Console Application u okviru prozora Templates: (za ovo ćete morati malo da pomerite prozor na dole). U okviru za tekst Location: promenite putanju u C:\Temp\SoftIng\LecturesCode\Vezba18 (ovaj će direktorijum biti automatski napravljen ukoliko već ne postoji), i ostavite podrazumevani tekst u okviru za tekst Name: Dogadjaji1 Dodajte sledeći kod u Class1.cs:
Kako to radi
Objekat koji koristimo za aktiviranje događaja je instanca klase System.Timers.Timer. Ovaj objekat je inicijalizovan vremenskim periodom (u milisekundama). Kada se objekat Timer startuje korišćenjem metode Start(), aktiviran je niz događaja razdvojenih vremenskim periodom koji je određen. Funkcija Main() inicijalizuje objekat Timer vremenskim periodom od 100 milisekundi, tako da će aktivirati 10 događaja u sekundi:
3
Objakat Timer ima događaj koji se zove Elipsed. Možemo ga videti ako koristimo prozor Object Broser:
Potpis rutine za obradu ovog događaja definisan je u tipu delegata System.Timers.ElapsedEventHandler, koji je jedan od standardnih delegata definisanih u .NET okruženju. Ovaj delegat se koristi za funkcije koje se podudaraju sa sledećim potpisom:
void functionName(object source, ElapsedEventArgs e);
Objekat Timer šalje referencu na samog sebe, u prvom parametra, i instancu ElapsedEventArgs klase u drugom parametru. Za sada možemo ignorisati ove parametre, ali pogledaćemo ih kasnije. U kodu imamo funkciju koja se podudara sa ovim potpisom:
Ova metoda koristi dva statička polja klase Class1, brojac i displayString, za prikaz jednog znaka. Za svaki sledeći poziv metode, znak koji se prikazuje biće različit.
4
Poslednje što moramo obaviti jeste da povežemo događaj i rutina za obradu događaja - da ga prijavimo. To postižemo operatorom += dodajući događaj rutinu za obradu događaja, u formi nove instance delegate inicijalizovane našom ratinom za obradu događaja:
Ova komanda (koja koristi malo čudnu sintaksu, svojstvenu delegatima) dodaje rutinu za obradu događaja u listu koja će biti pozvana kada se aktivira događaj Elapsed. Možemo dodati koliko god hoćemo rutina za obradu događaja u ovu listu, dokle god one zadovoljavaju postavljene kriterijume. Sve ratine za obradu događaja će biti pozvane kada se događaj aktivira. Sve što ostaje funkciji Main() jeste da pokrene Timer:
Pošto ne želimo da okončamo aplikaciju pre nego što obradimo bilo koji događaj, stavljamo funkciju Main() na čekanje. Najlakši način da ovo postignemo jeste kroz zahtev za unos podataka od strane korisnika, jer se obrada neće završiti dok korisnik ne unese red teksta i/ili pritisne Enter.
Iako se obrada u funkciji Main() završava ovde, obrada u objektu Timer se nastavlja. Kada ovaj objekat aktivira događaje, poziva se metoda WriteChar() koja se istovremeno izvršava sa Console.ReadLine() iskazom.
Pogledajmo kako da definišemo i koristimo naše sopstvene događaje. Implementiraćemo verziju primera za instant poruke sa početka ovog poglavlja. Kreiraćemo objekat Konekcija koji aktivira događaje obrađene od strane objekta Display. Primer definisanja događaja
Definisanje događaja
Vežba br. 19.
Napravite novi projekat konzolne aplikacije birajući File | New | Project... iz menija: Izaberite Visual C# Projects direktorijum unutar prozora Project Types:, i tip projekta Console Application u okviru prozora Templates: (za ovo ćete morati malo da pomerite prozor na dole). U okviru za tekst Location: promenite putanju u C:\Temp\SoftIng\LecturesCode\Vezba19 (ovaj će direktorijum biti automatski napravljen ukoliko već ne postoji), i ostavite podrazumevani tekst u okviru za tekst Name: Dogadjaji2 Dodajte sledeći kod u Class1.cs: Dodajte novu klasu Konekcija u Konekcija.cs:
5
Dodajte novu klasu Display u Display.cs:
6
Izmenite kodu Class1.cs:
Pokrenite aplikaciju.
7
Najveći posao u ovoj aplikaciji obavlja klasa Konekcija. Instance ove klase koriste objekte Timer, kao što smo videli u prvom primeru ovog poglavlja, inicijalizujući ih u konstruktoru klase. Takođe, obezbeđuju pristup statusu objekta Timer (omogućiti ili onemogućiti) kroz metode Connect() i Disconnect():
Kako to radi
Takođe, u konstruktoru prijavljujemo rutinu za obradu događaja za Elapsed događaj, kao što smo to radili u prvom primeru. Metoda CheckForMessage() aktivira događaj, u proseku jednom od svakih deset puta kada je pozvana. Pre nego što predemo na kod za ovu metodu, pogledajmo definiciju samog događaja. Pre nego što definišemo metodu, moramo definisati tip delegata koji se koristi sa događajem, tj. tip delegata koji određuje potpis koji mora imati i rutina za obradu događaja. To radimo uz pomoć standardne sintakse za delegate - definišemo ga kao javni tip unutar imenovanog prostora Dogadjaji2, da bi bio dostupan spoljašnjem kodu:
8
Ovaj tip delegata, koji smo nazvali MessageHandler, jeste potpis za void funkcije koje imaju jedan string parametar. Taj parametar možemo koristiti za prenos instant poruke primljene od objekta Konekcija do objekta Display. Kada je delegat definisan (ili je odgovarajući postojeći delegat lociran), možemo definisati i sam događaj kao član klase Konekcija:
Jednostavno imenujemo događaj (ovde smo koristili ime MessageArrived) i deklarišemo ga, koristeći ključnu reč event i tip delegata (tip delegate MessageHandler koji smo već definisali). Kada je događaj deklarisan na ovaj način, možemo ga aktivirati pozivajući ga po imenu, kao da se radi o metodu sa potpisom određenim od strane delegata. Na primer, možemo aktivirati događaj na sledeći način:
: Da je delegat definisan bez parametara, mogli smo napisati:
MessageArrived();
Mogli smo definisati i vise od jednog parametra, koji bi zahtevali vise koda za aktiviranje događaja. Naša metoda ProveraPoruke() izgleda ovako:
Koristimo instancu klase Random koju smo videli ranije, za slučajan izbor broja između 0 i 9, kao i za aktiviranje događaja ukoliko je izabrani broj 0, što će se desiti u 10% slučajeva. Ovo simulira ispitivanje veze radi provere da li je poruka stigla, što neće biti slučaj svaki put kada proveravamo. Skrećemo pažnju da događaj aktiviramo samo ako je izraz MessageArrived ! = null tačan. Ovaj izraz, koji koristi sintaksu delegata na neuobičajen način, u stvari znači: „Da li uopšte postoji nešto što bi aktiviralo događaj?" Ako tako nešto ne postoji, MessageArrived će uzeti vrednost null, tako da nema nikakvog smisla aktivirati događaj.
9
Klasa koja će reagovati kada je događaj aktiviran jeste klasa Display. Ona sadrži jednu metodu DisplayMessage():
Ova metoda ima odgovarajući potpis (i javna je, što je obavezno za ratine za obradu događaja koji ne pripadaju istoj klasi, kao i za objekat koji aktivira događaj), tako da je možemo koristiti za obradu događaja MessageArrived. Sve što preostaje da se uradi u funkciji Main() jeste inicijalizacija dve instance klasa Konekcija i Display, i njihovo povezivanje. Kod je sličan onome iz prvog primera:
Ponovo pozivamo metod Console.ReadLine() da pauzira obradu u funkciji Main() kada predemo na metodu Connect() objekta Konekcija.
Višenamenske rutine za obradu događaja
Potpis koji smo videli ranije za događaj Timer.Elapsed, sadrži dva parametra čiji se tipovi često sreću u rutinama za obradu događaja. Ti parametri su: object source - referenca na objekat koji aktivira događaj. ElapsedEventArgs e - parametri koje šalje događaj. Razlog zbog kojeg koristimo parametar tipa object za ovaj događaj, kao i za mnoge druge događaje, jeste taj što ćemo često hteti da koristimo jednu rutinu za obradu događaja za vise identičnih događaja aktiviranih od strane različitih objekata. Na ovaj način možemo tačno znati koji objekat aktivira događaj.
10
Proširimo malo poslednji primer.
Vežba br. 20.
Napravite novi projekat konzolne aplikacije birajući File | New | Project... iz menija: Izaberite Visual C# Projects direktorijum unutar prozora Project Types:, i tip projekta Console Application u okviru prozora Templates: (za ovo ćete morati malo da pomerite prozor na dole). U okviru za tekst Location: promenite putanju u C:\Temp\SoftIng\LecturesCode\Vezba20 (ovaj će direktorijum biti automatski napravljen ukoliko već ne postoji), i ostavite podrazumevani tekst u okviru za tekst Name: Dogadjaji3 Dodajte sledeći kod u Class1.cs: Kopirajte kod iz Class1.cs, Konekcija.cs i Display.cs iz projekta Dogadjaji2. Vodite računa da promenite imenovani prostor iz Dogadjaji2u Dogadjaji3. Dodajte novu klasu, MessageArrivedEventArgs, u datoteku MessageArrivedEventArgs.cs: using System; namespace Dogadjaji3
Izmenite Connection.cs:
11
Izmenite Display.cs:
12
Izmenite Class1.cs:
Pokrenite aplikaciju:
13
Kako to radi
Slanjem reference na objekat koji aktivira događaj kao jednog od parametra ratine za obradu događaja, možemo prilagoditi odgovor ratine na individualni objekat. Referenca daje pristup izvornom objektu, uključujući i njegova svojstva. Slanjem parametara koji su sadržani u klasi izvedenoj iz System.EventArgs (kao što je ElapsedEventArgs), možemo obezbediti bilo koje dodatne informacije koje su neophodne kao parametre (npr. parametar Poruka naše MessageArrivedEventArgs klase). Polimorfizam će dodatno koristiti parametrima. Mogli bismo definisati ratinu za obradu događaja MessageArrived:
Izmenite definiciju delegata u datoteci Kolekcija.cs:
Aplikacija će se izvršavati kao i pre, s tim da je funkcija DisplayMessage() mnogostrana (bar teoretski - dodatna implementacija bi bila neophodna za kvalitetan proizvod). Ista rutina može raditi i sa dragim događajima, kao što je događaj Timer.Elapsed. U tom slučaju, morali bismo izmeniti rutinu tako da parametri koji se šalju kada je događaj aktiviran budu valjano obrađeni (konverzija parametara u objekte Konekcija i MessageArrivedEventArgs na ovaj način izazvala bi izuzetak; trebalo bi koristiti operator as). Pre nego što krenemo dalje, primetite da se događaji aktiviraju u parovima, zbog dva objekta Konekcija. U pitanju je način na koji klasa Random generiše slučajne brojeve. Kada se kreira objekat Random, koristi se početna vrednost. Ova početna vrednost koristi kompleksnu
14
jednačinu za generisanje niza pseudoslučajnih brojeva. (Računari nisu u stanju da generišu prave slučajne brojeve.) Početnu vrednost za Random objekat možemo definisati u konstruktora, ali ako to ne uradimo (kao što i nismo), trenutno vreme se koristi kao početna vrednost. Pošto formiramo dva Connection objekta u dva reda koda, koji idu jedan za dragim, postoji velika verovatnoća da će se koristiti iste početne vrednosti kada objekti odgovaraju na događaj Timer.Elapsed. Ako jedan objekat generiše poruku, vrlo je verovatno da će i dragi to činiti, što je posledica brzine obrade. Da bismo rešili problem, moramo drugačije podesiti početne vrednosti. Jedno rešenje, koje ovde nećemo raditi, bilo bi da se obezbedi instanca klase Random kojoj se može pristupiti iz oba objekta Konekcija. Objekti bi, u tom slučaju, koristili isti niz slučajnih brojeva, što bi bitno umanjilo šansu za prikazivanje sinhronizovanih poruka.
Povratne vrednosti i rutina za obradu događaja
Svi moduli koje smo do sada videli imali su povratni tip void. Moguće je obezbediti neki dragi tip, ali to dovodi do problema zato što neki događaj može imati za posledicu pozivanje nekoliko rutina za obradu događaja. Ako sve ove ratine vrate neku vrednost, postaje nejasno koju od vrednosti uzimamo. Sistem rešava problem tako što mm dozvoljava pristup samo poslednjoj vraćenoj vrednosti. Ovo je vrednost koju vraća poslednja ratina za obradu događaja koja se odnosi na neki događaj. Možda ovo rešenje ima upotrebnu vrednost u nekoj situaciji, ali ipak preporučujemo korišćenje povratnog tipa void za ratine za obradu događaja, kao i izbegavanje korišćenja parametara tipa out.
15
..."
|
You need to upgrade your Flash Player , or try to enable javascript in order see this document properly.
|
|