Press "Enter" to skip to content

[c#] Zdarzenia – ostatni z konkursowych

Zdarzenia są wywoływane, jeżeli coś konkretnego stanie się w trakcie działania naszego programu. Akcją wywołującą je może być osiągnięcie przez zmienną pewnej wartości, przejście wykonania programu do określonego bloku, użycie mechanizmu drag and drop, lub chociażby kliknięcie na przycisk lub najechanie myszką na dane pole na ekranie. To właśnie dzięki zdarzeniom dany obiekt może poinformować inny (który dane publiczne zdarzenie obsługuje) o zmianie swojego stanu. Klasa naszego obiektu może mieć bardzo wiele zdarzeń, które są wywoływane po zaistnieniu ściśle określonego warunku (jak zakończenie pobierania pliku, przyciśnięcie przycisku czy ustawienie zmiennej w setterze). Użytkownik pisząc w Visual Studio często sobie takie zdarzenia „wyklikiwał” projektując interfejs – na przykład przeciągał z toolboxa kontrolkę typu button, nadając jej nazwę, a następnie klikając na nią 2 razy. Po takiej operacji został wygenerowany następujący kod:

 
private void button1_Click(object sender, RoutedEventArgs e)
        {
            
        }

Jak widać funkcją obsługującą zdarzenie jest (i musi być) funkcja, która nie zwraca wartości, a pobiera obiekt, który wywoła zdarzenie, oraz obiekt typu EventArgs. W przykładzie powyżej mamy obiekt typu RoutedEventArgs. Najeżdżając na nazwę tego typu i po wciśnięciu F12 możemy zobaczyć taki fragment kodu public class RoutedEventArgs : EventArgs .
Jak widzimy, klasa RoutedEventArgs dziedziczy po EventArgs – czyli wszystko jest jak być powinno. Powyższą klasę należy wypełnić kodem, który zostanie wywołany przy zajściu zdarzenia – kliknięcia na przycisk o nazwie button1.
Aby nasza funkcja obsłużyła dane zdarzenie należałoby dokonać subskrypcji poprzez dopisanie delegaty do listy delegat, czyli zdarzenia. W przykładzie powyżej nie musieliśmy tego robić? Nie prawda, też musieliśmy, ale zrobiła to za nas platforma. W pliku MojaKlasa.g.cs (przy nowym projekcie MainWindow.g.cs), który jest generowany na podstawie naszego pliku .cs oraz .xaml czytamy:

 
#line 6 "..\..\..\MainWindow.xaml"
this.button1.Click += new System.Windows.RoutedEventHandler(this.button1_Click);

W powyższym kodzie widzimy utworzenie delegatu do funkcji podanej w parametrze. Ogólnie robi się to tak:

 
public delegate void TypDelegatu();

poza klasami, a potem gdzieś w środku funkcji

  
TypDelegatu JakisHandler = new TypDelegatu (JakasMetoda);
obiekt.JakisEvent += JakisHandler;
lub po prostu obiekt.JakisEvent += new TypDelegatu(jakasMetoda);

Bezpieczniej jest użyć += niż =, ponieważ nie wiemy, czy coś już zostało do zdarzenia dopisane. Możemy do obsługi zdarzenia dodać kilka funkcji, wystarczy dodać obiekt.JakisEvent += new TypDelegatu(InnaMetoda);
Metody wywołają się w takiej kolejności, w jakiej zostały dopisane do obsługi zdarzenia, w kolejności od najwcześniej dodanej, do najpóźniej.
Event jest niczym innym, jak własnością klasy, która może go wygenerować. W takim razie, deklarujemy go (zwykle publicznie) w ciele klasy. Na przykład

public event JakisHandler JakisEvent;

Przy założeniach, że JakisHandler to typ delegatu.
Napiszmy teraz własne zdarzenie i je obsłużmy. Najprościej będzie to zrobić umieszczając wywołanie zdarzenia w setterze i zareagowanie na zmianę. Widzimy to w poniższym kodzie

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    public enum swiatlo { czerwone, zolte, zielone };

    public delegate void RedHandler();
    public delegate void YellowHandler();
    public delegate void GreenHandler();

    public class sygnalizator
    {
        private swiatlo _kolorswiatla;

        public event RedHandler Redevent;
        public event YellowHandler Yellowevent;
        public event GreenHandler Greenevent;

        public swiatlo kolorswiatla
        {
            get { return _kolorswiatla; }
            set
            {
                _kolorswiatla = value;
                if (value == swiatlo.zielone) if (Greenevent != null) Greenevent(); //sprawdzamy czy są jakieś zdarzenia by uniknąć NullPointerExeption
                if (value == swiatlo.czerwone) if (Redevent != null) Redevent();
                if (value == swiatlo.zolte) if (Yellowevent != null) Yellowevent();
            }
        }

    }
    class Program
    {
        static void Beep() { for (int i=0; i<3; i++) Console.Beep(); }
        static void Redalert() { Console.WriteLine("światło czerwone"); }
        static void Greenalert() { Console.WriteLine("światło zielone"); }
        static void Yellowalert() { Console.WriteLine("światło żółte"); }
        static void Main(string[] args)
        {
            //Stworzenie obiektu
            sygnalizator Sygnalizator = new sygnalizator();
            //dodanie obsługi zdarzeń
            Sygnalizator.Greenevent+=new GreenHandler(Greenalert);
            Sygnalizator.Yellowevent += new YellowHandler(Yellowalert);
            Sygnalizator.Redevent += new RedHandler(Redalert);
            Sygnalizator.Redevent += new RedHandler(Beep);
            Sygnalizator.kolorswiatla = swiatlo.czerwone;
            System.Threading.Thread.Sleep(5000);
            Sygnalizator.kolorswiatla = swiatlo.zolte;
            System.Threading.Thread.Sleep(1000);
            Sygnalizator.kolorswiatla = swiatlo.zielone;
            Console.ReadKey();
        }
    }
}

Klasa wywołująca zdarzenie, klasa w której zdarzenie zostało wywołane oraz użycie obu klas, mogą być zupełnie w innych miejscach. Wyobraźmy sobie lotnisko (program główny), na którym stoi kazio i obserwuje lecący samolot (subskrybuje powiadomenia o stanie samolotu). Moglibyśmy to zaimplementować w następujący sposób:

 
using System;
using System.Threading;
 
namespace Okecie
{
     public class Samolot
    {
        public delegate void LotHandler (object samolot, EventArgs info);
        public event LotHandler Lot;
        protected void OnLot(object samolot, EventArgs info)
        {
            if (Lot != null)
            {
                Lot(this, info);
            }
        }
        public void odlot(int a)
        {
            for (int i = 0; i < a; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Samolot leci");
                OnLot(this, new EventArgs());
            }
            Console.WriteLine("Samolot doleciał");
            OnLot(this, new EventArgs());

        }
    }
 
    public class Obserwator
    {
        public void Subscribe(Samolot machine)
        {
            machine.Lot += new Samolot.LotHandler(CheckResult);
        }
        public void CheckResult(object samolot, EventArgs info)
        {
            Console.WriteLine();
        }
    }
 
    class Lotnisko
    {
        static void Main(string[] args)
        {
            Samolot samolocik = new Samolot();
            Obserwator kazio = new Obserwator();
 
            kazio.Subscribe(samolocik);
            samolocik.odlot(10);
            Console.ReadKey();
        }
    }
}

Mamy tu użycie zdarzeń, które sami napisaliśmy, na obiektach naszej produkcji.
Jak widać, użycie zdarzeń jest naprawdę prostsze niż można by domniemać oglądając przykład użycia prezentowany w dokumentacji MSDN. W bardzo łatwy sposób możemy używać zdarzeń, jakie zostały dla nas przygotowane przez platformę, oraz tworzyć swoje. Dzięki zdarzeniom nasza klasa może wywołać funkcje, o jakich nie ma zielonego pojęcia w chwili jej tworzenia – ważne tylko, by przyjmowała określone parametry.