Własne zdarzenia (events)

0

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ę.

7

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 nilować 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? ;)

4
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.

0

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ść.

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0