Mógłby ktoś wytłumaczyć mi na prostym przykładzie jak stworzyć własne zdarzenia. W internecie znalazłem tylko przykłady po angielsku. Niestety ich nie rozumiem. Z góry dziękuję.
Zdarzenie można uprościć do dwóch elementów — pierwszy to pole przechowujące adres metody, a drugi to właściwość, dzięki której daje się dostęp do tej zmiennej. Adres metody siedzącej w zmiennej może być odczytywany i modyfikowany za pomocą właściwości. Piszę „metody”, bo adres musi wskazywać na metodę, czyli procedurę/funkcję będącą częścią klasy. Zdarzenie może być bezparametrowe, ale może zawierać dowolną liczbę i typy parametrów. Przyjęło się, że każde zdarzenie posiada parametr Sender
, który dostaje referencję klasy wywołującej owe zdarzenie.
Do wywoływania zdarzenia, dodatkowo deklaruje się wewnętrzną metodę z prefiksem Do*
, która sprawdza czy zmienna zawiera jakiś adres i jeśli tak, to je wywołuje, przekazując dane w parametrach. Taka jest przyjęta konwencja — tak w skrócie.
Załóżmy, że chcesz sobie dodać zdarzenie, które będzie w parametrach przekazywało jakąś liczbę i ciąg znaków — niech nazywa się OnUpdateItem
(nazwa losowa). Te dwa elementy, czyli liczba i ciąg znaków, są polami w klasie:
type
TSomeClass = class(TObject)
private
FFoo: Integer;
FBar: String;
{..}
end;
Teraz chcemy dodać zdarzenie, które przekaże te dwie wartości użytkownikowi. Nasze zdarzenie ma mieć trzy parametry — Sender
, Foo
i Bar
— dlatego pasuje najpierw zadeklarować typ takiego zdarzenia. Robi się to w ten sposób:
type
TOnUpdateItem = procedure(ASender: TObject; AFoo: Integer; const ABar: String) of object;
Taka konstrukcja oznacza, że zmienna tego typu może przyjąć adres procedury posiadającej wymienione parametry, i która to procedura jest częścią klasy (stąd końcówka of object
). Mamy już typ danych, więc możemy zadeklarować sobie pole:
type
TSomeClass = class(TObject)
private
FFoo: Integer;
FBar: String;
private
FOnUpdateItem: TOnUpdateItem;
{..}
end;
Wartości tego pola nie trzeba nil
ować w konstruktorze klasy, dlatego że wszystkie pola klas są automatycznie inicjalizowane wartościami domyślnymi w trakcie tworzenia instancji. W przypadku zmiennych dla zdarzeń, automatycznie przyjmują wartość nil
. Ok, teraz tylko zostało napisać właściwość:
type
TSomeClass = class(TObject)
private
FFoo: Integer;
FBar: String;
private
FOnUpdateItem: TOnUpdateItem;
{..}
published
property OnUpdateItem: TOnUpdateItem read FOnUpdateItem write FOnUpdateItem;
end;
Właściwości zdarzeniowe zwykle łączą bezpośrednio z polem (brak setterów i getterów), dając możliwość odczytu i modyfikacji. Ostatnim elementem jest napisanie metody Do*
(u nas to DoUpdateItem
), która będzie sprawdzać referencję i wywoływać metodę:
type
TSomeClass = class(TObject)
private
FFoo: Integer;
FBar: String;
private
FOnUpdateItem: TOnUpdateItem;
private
procedure DoUpdateItem();
{..}
published
property OnUpdateItem: TOnUpdateItem read FOnUpdateItem write FOnUpdateItem;
end;
{..}
procedure TSomeClass.DoUpdateItem();
begin
if Assigned(FOnUpdateItem) then
FOnUpdateItem(Self, FFoo, FBar);
end;
I teraz w każdym miejscu tej klasy, w dowolnej metodzie, jeśli trzeba będzie wywołać zdarzenie OnUpdateItem
, bo wewnętrzne mechanizmy klasy tego wymagają, to wywołuje się po prostu metodę DoUpdateItem
.
W przykładzie wyżej zadeklarowałem właściwość zdarzeniową w sekcji published
, tak aby była ona widoczna w oknie Inspektora Obiektów. Nie wiem czy interesuje Cię zdarzenie w klasie komponentu czy w zwykłej klasie, więc wybrałem taki przykład. W razie czego, właściwości mogą być deklarowane w dowolnej sekcji, ale tylko te z sekcji published
mogą być wyświetlone w oknie Inspektora, bo da się je odszukać za pomocą RTTI.
No i to w sumie wszystko, jeśli chodzi o podstawy. Masz jakieś pytania? ;)
furious programming napisał(a):
type TOnUpdateItem = procedure(ASender: TObject; AFoo: Integer; const ABar: String) of object;
Taka konstrukcja oznacza, że zmienna tego typu może przyjąć adres procedury posiadającej wymienione parametry, i która to procedura jest częścią klasy (stąd końcówka
of object
).
Drobna uwaga; metoda obsługi zdarzenia nie musi być procedurą, może być też funkcją która zwraca jakąś wartość.
A więc może wyglądać to np. tak:
type
TOnUpdateItem = function(ASender: TObject; AFoo: Integer; const ABar: String) : string of object;
Mam wrażenie, że nie wszyscy o tym wiedzą, a taka konstrukcja bywa przydatna.
To prawda — w końcu to tylko wskaźnik na metodę (tak z grubsza). Ale muszę przyznać, że nie spotkałem się jeszcze z funkcyjnymi zdarzeniami. Jeśli już użytkownik może wprowadzać zmiany, to z reguły są to procedury z parametrami typu var
, które to są już zainicjalizowane.
Z funkcjami jest ten problem, że użytkownik musi ustalić rezultat, a jak tego nie zrobi, to funkcja zwróci losowe dane i zachowanie klasy przestanie być przewidywalne. Przerzucanie takiej odpowiedzialności na użytkownika jest ryzykowne i chyba z tego powodu funkcyjne zdarzenia to rzadkość.