Zdarzenia - Tomasz Kubik
Transkrypt
Zdarzenia - Tomasz Kubik
Zdarzenia Deklaracja zdarzeń Zdarzenia mogą być zadeklarowane przez użytkownika. Odpowiednia składnia deklaracji zdarzenia wygląda następująco: [ <attrlist> ] [ Public | Private | Protected | Friend | Protected Friend ] _ [ Shadows ] Event eventname[(arglist)] _ [ Implements interfacename.interfaceeventname ] Jak widać, zdarzenia mogą być deklarowane z parametrami, podobnie jak deklarowane są procedury z parametrami. Jednak w przypadku zdarzeń istnieje ograniczenie w stosowaniu parametrów. Otóż zdarzenia nie mogą mieć nazwanych argumentów ani argumentów opcjonalnych (Optional). Zdarzenia deklaruje się wewnątrz klas, struktur, modułów lub interfejsów. Przykładowo: Event AnEvent(ByVal EventNumber As Integer) Wywoływanie zdarzeń Zdarzenia są jak wiadomości, które przychodzą w chwilach, kiedy dzieje się coś ważnego (tj. asynchronicznie). Rozgłaszanie wiadomości wszystkim zainteresowanym nazywa się wywoływaniem zdarzeń (rising the event). W VB.NET zdarzenia wywołuje się za pomocą wyrażenia RaiseEvent: RaiseEvent AnEvent(EventNumber) Typowe użycie zdarzenia, tj. jego deklaracja oraz zgłoszenie, realizowane jest jak niżej: Public Class EventSource ' Deklaracja zdarzenia Public Event LogonCompleted(ByVal UserName As String) ' Deklaracja metody, w której wywoływane jest zdarzenie. Sub CauseEvent() ' Wywołanie zdarzenia. RaiseEvent LogonCompleted("AustinSteele") End Sub End Class Zdarzenia powinny być wywoływane wewnątrz bloku, w którym zostały zadeklarowane. Wynika stąd, że klasy pochodne nie mogą wywoływać zdarzeń odziedziczonych ze swoich klas bazowych. Podobnie nie można wywoływać zdarzeń, które nie są jawnie zadeklarowane w module. Ponieważ parametry zdarzeń mogą być przekazywane ByRef, może zdarzyć się, że procedura obsługi zdarzenia otrzyma parametry zmodyfikowane przez wcześniejszą procedurę obsługi zdarzenia. Zdarzenia, które nie są współdzielone w klasie (nie są Shared) nie powinny być wywoływane w konstruktorach klas, w których je zadeklarowano. Jeśli byłyby one wywołane, to istnieje możliwość, że pozostaną nieobsłużone. Dlatego wszystkie zdarzenia wywoływane w konstruktorze powinny być Shared. Źródła zdarzeń (event senders, event sources) Obiekt zdolny do wywołania zdarzenia jest źródłem zdarzeń. Przykładami źródeł zdarzeń mogą być formy, kontrolki oraz obiekty zdefiniowane przez użytkownika. Słuchacze zdarzeń (event handlers) Obiekt klasy, która wywołuje zdarzenie, jest źródłem zdarzenia, metody zaś, które przetwarzają to zdarzenie nazywane są metodami obsługi zdarzeń lub słuchaczami zdarzeń (event handlers). Zdarzenie wywoływane przez źródło zdarzeń może być obsługiwane przez wiele metod. Kiedy źródło zdarzeń wywoła zdarzenie, zdarzenie to zostanie przekazane do wszystkich zadeklarowanych procedur obsługujących to zdarzenie (tzn. do metod instancji klas zadeklarowanych w przykładzie z WithEvents). W przypadku wywoływania wielu zdarzeń, zdarzenia trafiają do kolejki zdarzeń. Jeśli nie zadeklarowano inaczej, w danym momencie wykonywana jest jedna procedura obsługi zdarzeń, która trafiła do kolejki najwcześniej. Znaczy to, że dopóki wykonywana jest dana procedura obsługi zdarzeń, żadne inne zdarzenie nie zostanie obsłużone. Stąd może się zdarzyć, że ze względu na długą obsługę jakiegoś zdarzenia okienko aplikacji nie odświeży się. Aby dopuścić do obsługi wielu zdarzeń równocześnie stosuje się polecenie Application.DoEvents(). Słuchaczami zdarzeń są metody, które zajmują się obsługą wywołanych zdarzeń. Każda poprawna procedura może być procedurą obsługi zdarzeń. Procedura taka zostanie wywołana z parametrami, z jakimi wywołane zostanie zdarzenie przez źródło zdarzeń. Dopóki procedura obsługi zdarzenia się nie skończy, aplikacja nie będzie mogła obsłużyć i VB stosuje standardową konwencję nazw dla procedur obsługi zdarzeń, w której po nazwie źródła zdarzeń i podkreślniku występuje nazwa zdarzenia. Na przykład dla przycisku o nazwie button1 procedura obsługi zdarzenia Click powinna nazywać się Sub button1_Click. Konwencja ta jest zalecana, jednak nie jest obowiązkowa. Do obsługi zdarzeń nie można używać funkcji (gdyż wartości zwracane przez funkcje nie mogą być zwrócone do źródła zdarzeń). W poniższym przykładzie (wziętym z dokumentacji MSDN) zdarzenia używane są do zliczania sekund czasu trwania rekordowego biegu na 100 metrów. W dostarczonym kodzie można zauważyć fragmenty związane ze zdarzeniami jak: metody, właściwości i wyrażeniami zawierające RaiseEvent. W przykładzie użyta jest forma (Form1) z przyciskiem (Command1), etykietą (Label1) oraz dwoma polami tekstowymi (Text1 oraz Text2). Po uruchomieniu program będzie oczekiwał na kliknięcie na przycisku "Kliknij by zacząć bieg". Kiedy użytkownik kliknie na przycisku, w pierwszym z pól tekstowych pojawi się napis "Od teraz" i zacznie się odliczanie sekund. Kiedy upłynie 9.84 sekundy, w pierwszym polu tekstowym pojawi się napis "Do teraz" zaś w drugim "9.84". W kodzie Form1 określone są wartości początkowe i końcowe stanów kontrolek na formie. Jest tam też kod obsługujący wywoływane zdarzenia. Aby prześledzić działanie programu należy otworzyć nowy projekt z formą zawierającą wymienione wcześniej kontrolki (Button1, Label1, Text1, Text2, TextBox1 oraz form1). Automatycznie wygenerowany kod dla formy zawierać będzie fragment (Windows Form Designer generated code). Fragment ten należy pozostawić niezmieniony, zaś cała reszta kodu powinna wyglądać jak niżej: Imports Microsoft.VisualBasic.DateAndTime Public Class Form1 Inherits System.Windows.Forms.Form Windows Form Designer generated code Private WithEvents mText As TimerState Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load Button1.Text = "Kliknij by zacząć bieg" TextBox1.Text = "" TextBox2.Text = "" Label1.Text = "Czas trawania rekordowego biegu na 100 metrów:" mText = New TimerState End Sub Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click TextBox1.Text = "Od początku" TextBox1.Refresh() TextBox2.Text = "0" TextBox2.Refresh() mText.TimerTask(9.84) End Sub Private Sub mText_ChangeText() Handles mText.ChangeText TextBox1.Text = "Do końca" TextBox2.Text = "9.84" End Sub Private Sub mText_UpdateTime(ByVal Jump As Double) _ Handles mText.UpdateTime TextBox2.Text = Format(Jump, "##0.00") Application.DoEvents() End Sub Class TimerState Public Event UpdateTime(ByVal Jump As Double) Public Event ChangeText() Public Sub TimerTask(ByVal Duration As Double) Dim Start As Double Dim Second As Double Dim SoFar As Double Start = Timer SoFar = Start Do While Timer < Start + Duration If Timer - SoFar >= 0.1 Then SoFar = SoFar + 0.1 RaiseEvent UpdateTime(Timer - Start) End If Loop RaiseEvent ChangeText() End Sub End Class End Class Implementacja obsługi zdarzeń Powiązanie zdarzeń ze słuchaczami zdarzeń Istnieją dwa sposoby na skojarzenie zdarzenia ze słuchaczem zdarzeń: użycie klauzuli Handles (razem z wyrażeniem WithEvents) lub wyrażenia AddHandler (oraz RemoveHandler). Wyrażenie WithEvents i klauzula Handles dostarczają deklaratywnego sposobu określania słuchaczy zdarzeń. Zdarzenia wywoływane przez obiekt zadeklarowany z użyciem WithEvents może być obsługiwane przez dowolną procedurę opatrzoną klauzulą Handles z nazwą zdarzenia. Chociaż klauzula Handles jest standardową metodą na powiązanie zdarzeń ze słuchaczami zdarzeń, jest ona ograniczona do powiązań pomiędzy zdarzeniami i ich słuchaczami znanymi w czasie kompilacji. Wyrażenia AddHandler oraz RemoveHandler są bardziej elastyczne niż klauzula Handles. Pozwalają one dynamicznie wiązać i usuwać powiązania pomiędzy zdarzeniami i jednym lub więcej słuchaczami w czasie wykonywania oraz nie wymagają deklaracji obiektów wywołujących zdarzenia za pomocą WithEvents. Obsługa zdarzeń za pomocą WithEvents WithEvents pozwala utworzyć klasą lub zmienne obiektowe poziomu modułu, które można używać z klauzulą Handles do obsługi zdarzeń. Aby zaimplementować obsługę zdarzeń w tym schemacie należy: 1. Zadeklarować klasę, która będzie źródłem zdarzeń (tzn. będzie posiadać zdarzenia i będzie wywoływać zdarzenia) Class Klasa1 Public Event Zdarzenie(ByVal s As String) Public Sub WywolajZdarzenie() RaiseEvent Zdarznie("To jest parametr zdarzenia") End Sub End Class 2. Użyć WithEvents do deklaracji referencji do obiektu służącego jako źródło zdarzeń w sekcji deklaracji modułu (klasy), który będzie obsługiwał zdarzenie: Public WithEvents mObiekt As Klasa1 Aby można było użyć tej referencji (i wywołać zdarzenie) należy gdzieś utworzyć obiekt. Można to zrobić np. w procedurze Form1_Load() lub konstruktorze klasy, której parametrem jest mObiekt: mObiekt = New Klasa1() 3. W rozwijalnych listach (dwie listy w VB.NET umieszczone są one nad okienkiem z kodem źródłowym) wybrać po lewej stronie właśnie zadeklarowaną zmienną, zaś po prawej stronie nazwę zdarzenia, dla którego będzie tworzona procedura obsługi zdarzeń. Krok ten jest opcjonalny, gdyż procedurę obsługi zdarzenia można napisać samemu. Warunkiem koniecznym w takim przypadku byłoby zachowanie poprawnej listy parametrów oraz poprawnej składni (z klauzulą Handles). 4. Zaimplementować kod procedury obsługi zdarzenia. Jeśli obiekt oraz nazwa zdarzenia zostały wybrane z list wyboru, środowisko wygeneruje pustą implementację takiej procedury: Private Sub mObiekt_Zdarzenie(ByVal s As String) Handles mObiekt.Zdarzenie ' tutaj należy wpisać kod obsługi zdarzenia End Sub W środowisku VB.NET niektórzy słuchacze zdarzeń automatycznie wiązani są ze zdarzeniami (porównaj z krokiem 3 powyżej). Powiązanie to pojawia się jako pusta procedura obsługi zdarzenia. Dla przykładu, jeśli na widoku formy umieszczony zostanie przycisk, to podwójne kliknięcie na nim daje w efekcie pustą procedurę obsługi zdarzenia oraz odpowiednia zmienna typu Button dostaje w deklaracji WithEvents jak w poniższym przykładzie: Friend WithEvents Button1 As System.Windows.Forms.Button Protected Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click End Sub Ograniczenia w używaniu wyrażenia WithEvents 1. WithEvents nie można używać do obiektowych zmiennych generycznych. Znaczy to, że zmienne, które deklarowane są z użyciem WithEvents nie mogą być typu Object (typem musi być konkretna klasa). 2. Nie można używać WithEvents do deklaratywnego obsługiwania współdzielonych wyjątków (współdzielone wyjątki nie są związane z instancją, lecz z klasą, a WithEvents stosuje się do instancji). 3. Nie można tworzyć tablic zmiennych WithEvents. 4. Zmienne WithEvents pozwalają pojedynczej procedurze obsługi zdarzeń obsługiwać jeden lub kilka typów wyjątków, lub jednej lub więcej procedurom obsługi zdarzeń obsługiwać zdarzenie tego samego typu. Dodawanie i usuwanie słuchaczy zdarzeń (użycie AddHandler oraz RemoveHandler) Wyrażenie AddHandler podobne jest do klauzuli Handles w tym, że pozwala określić słuchacza zdarzeń, który obsłuży dane zdarzenie. Jednak AddHandler (razem z RemoveHandler) dostarcza więcej możliwości niż klauzula Handles, pozwalając na dynamiczne dodawanie, usuwanie, zmienianie słuchaczy błędów związanych ze zdarzeniem. W odróżnieniu od Handles, AddHandler pozwala powiązać wielu słuchaczy zdarzeń z pojedynczym zdarzeniem. AddHandler pobiera dwa argumenty: nazwę zdarzenia od źródła zdarzenia (jakim może być kontrolka) oraz wyrażenie, które określa delegata. Używając AddHandler nie ma konieczności jawnego określania klasy delegata. Zamiast tego stosuje się wyrażenie AddressOf zwraca referencję do delegata. W poniższym przykładzie pokazano, jak powiązać słuchacza zdarzeń z wywoływanym przez obiekt zdarzeniem: AddHandler MyObject.Event1, AddressOf Me.MyEventHandler Aby usunąć powiązanie zdarzenia ze słuchaczem należy użyć RemoveHandler, które to wyrażenia ma taką samą składnię jak AddHandler: RemoveHandler MyObject.Event1, AddressOf Me.MyEventHandler Uwaga: Implementacja obsługi jednego zdarzenia nie powinno być implementacją mieszaną, tj. nie powinno się używać wyrażenia WithEvents oraz AddHandler dla tego samego zdarzenia. Obsługa zdarzeń za pomocą AddHandler Wyrażenie AddHandler pozwala dynamicznie wiązać słuchaczy zdarzeń ze zdarzeniami. Aby użyć tego sposobu obsługi zdarzeń należy: 1. Zadeklarować klasę, która będzie źródłem zdarzeń (tzn. będzie posiadać zdarzenia i będzie wywoływać zdarzenia). 2. Zadeklarować referencję do obiektu klasy będącej źródłem zdarzeń, które chcemy obsługiwać. Tym razem nie musi być to zmienna zadeklarowana jako WithEvents. Może to być zmienna lokalna w procedurze: Dim mObiekt As New Klasa1() Deklaracja jak wyżej tworzy od razu obiekt źródła zdarzeń. 3. Użyć wyrażenia AddHandler, aby określić nazwę źródła zdarzeń oraz użyć wyrażenia AddressOf, aby określić procedurę obsługi zdarzenia: AddHandler mObiekt.Zdarzenie, AddressOf obsluga_Zdarzenie Do obsługi zdarzeń można wykorzystać dowolną procedurę, której lista argumetów zgadza się z listą argumentów zdarzenia. W tym przypadku będzie to procedura o nazwie obsluga_Zdarzenie. 4. Zaimplementować kod procedury obsługi zdarzenia (bez klauzuli Handles): Public Sub obsluga_Zdarzenie(ByVal s As String) ' tutaj należy wpisać kod obsługi zdarzenia End Sub Użycie RemoveHandler do zatrzymania obsługi zdarzeń RemoveHandler używane jest do usuwania powiązań procedur obsługi zdarzeń ze zdarzeniami. Aby to zrobić należy: 1. Użyć wyrażenia RemoveHandler, aby określić źródło zdarzeń oraz aby określić (za pomocą AddressOf) procedurę obsługi zdarzeń. Składnia tego wyrażenia jest podobna do składni AddHandler: RemoveHandler mObiekt.Zdarzenie, AddressOf obsluga_Zdarzenie Obsługa zdarzeń w klasach dziedziczących zdarzenia Wcześniej powiedziane było, że klasy dziedziczące zdarzenia nie mogą być źródłami zdarzeń zadeklarowanych w ich klasach bazowych. Mogą one jednak obsługiwać takie zdarzenia. Aby zadeklarować procedurę obsługi zdarzenia generowanego przez klasę bazową, w klasie pochodnej deklaruje się metodę obsługi za pomocą wyrażenia o składni Handles MyBase.<event name> Samo napisanie obsługi zdarzeń w klasach pochodnych odbywa się wg schematu: 1. Zadeklaruj klasę będącą źródłem zdarzeń Public Class Class1 Public Event SE(ByVal i As Integer) ' Tutaj zaimplementuj metody wywołujące zdarzenia End Class 2. W klasie pochodnej zadeklaruj procedurę używając wyrażenia Handles MyBase.<event name> Public Class Class2 Inherits Class1 Sub EventHandler(ByVal x As Integer) Handles MyBase.SE ' Tutaj zaimplementuj sposób obsługi zdarzenia. End Sub End Class Przykład z jedną procedurą obsługi zdarzeń obsługującą dwa zdarzenia: Dim WithEvents EClass As New EventClass ' Declare a WithEvents variable. Sub TestEvents() EClass.RaiseEvents() End Sub ' Declare an event handler that handles multiple events. Sub EClass_EventHandler() Handles Eclass.XEvent, Eclass.YEvent MsgBox("Received Event.") End Sub Class EventClass Public Event XEvent() Public Event YEvent() Sub RaiseEvents() 'Raises two events handled by EClass_EventHandler. RaiseEvent XEvent() RaiseEvent YEvent() End Sub End Class