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