Skip to content

8. Kullanıcı olayları

Bir önceki bölümde, form bileşenleriyle ilgili olay kavramını ele almıştık. Şimdi ise kendi sınıflarımızda nasıl olaylar oluşturacağımızı göreceğiz.

8.1. Önceden tanımlanmış delege nesneleri

delegate nesnesi kavramı önceki bölümde ele alınmıştı, ancak o zaman sadece üstünkörü geçilmişti. Bir formun bileşenlerinin olay işleyicilerinin nasıl tanımlandığını incelediğimizde, aşağıdakine benzer bir kodla karşılaşmıştık:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

burada buttonAfficher, [Button] türünde bir bileşendi. Bu sınıfta, Click alanı şu şekilde tanımlanmıştır:

  • [1]: [Button] sınıfı
  • [2]: olayları
  • [3,4]: Click olayı
  • [5]: [Control.Click] [4] olayının bildirimi.
    • EventHandler, delegate olarak adlandırılan bir yöntem prototipidir (modeldir).
    • event, delegate ve EventHandler'in işlevlerini kısıtlayan bir anahtar kelimedir: bir delegate nesnesi, bir event nesnesinden daha zengin işlevlere sahiptir.

delegate ve EventHandler şu şekilde tanımlanmıştır:

 

delegate ve EventHandler, bir yöntem şablonunu ifade eder:

  • 1. parametresi Object türü olan
  • 2. parametresi EventArgs türündedir
  • hiçbir sonuç döndürmez

EventHandler ile tanımlanan modele karşılık gelen bir yöntem şu şekilde olabilir:


        private void buttonAfficher_Click(object sender, EventArgs e);

EventHandler türünde bir nesne oluşturmak için aşağıdaki adımlar izlenir:

EventHandler evtHandler=new EventHandler(méthode correspondant au prototype  défini par le type EventHandler);

Böylece şu şekilde yazılabilir:

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

delegate türündeki bir değişken, aslında delegate modeline karşılık gelen yöntemlere ilişkin bir referans listesidir. Yukarıdaki evtHandler değişkenine yeni bir M yöntemi eklemek için şu sözdizimi kullanılır:

evtHandler+=new EvtHandler(M);

evtHandler boş bir liste olsa bile += notasyonu kullanılabilir.

Aşağıdaki komut:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

buttonAfficher.Click olayının yöntem listesine EventHandler türünde bir yöntem ekler. buttonAfficher bileşeninde Click olayı meydana geldiğinde, VB şu komutu yürütür:

            buttonAfficher.Click(source, evt);

burada:

  • source, object türündeki ve EventArgs türündeki source olayını tetikleyen bileşendir
  • evt'i tetikleyen bileşendir ve bilgi içermez

Click olayına şu şekilde ilişkilendirilmiş tüm void M(object,EventArgs) imza yöntemleri:


            this.buttonAfficher.Click += new System.EventHandler(M);

VB tarafından iletilen (source, evt) parametreleriyle çağrılacaktır.

8.2. Delegate nesnelerini tanımlama

Şu komut


    public delegate int Opération(int n1, int n2);

komutu, iki tamsayı alan ve bir tamsayı döndüren bir işlev prototipini temsil eden Opération adlı bir türü tanımlar. Opération'i bir işlev prototipi tanımı haline getiren, delegate anahtar sözcüğüdür.

Opération türündeki bir op değişkeni, Opération prototipine karşılık gelen işlevlerin listesini kaydetme görevini üstlenecektir:

int f1(int,int)
int f2(int,int)
...
int fn(int,int)

fi yönteminin op değişkenine kaydedilmesi şu şekilde yapılır: op=new Opération(fi) komutuyla veya daha basitçe op=fi. komutuyla gerçekleştirilir. Zaten kaydedilmiş fonksiyonlar listesine bir fj yöntemi eklemek için op+= fj yazılır. Zaten kaydedilmiş bir fk yöntemini kaldırmak için op-=fk yazılır. Örneğimizde n=op(n1,n2) yazılırsa, op değişkeninde kayıtlı tüm yöntemler, n1 ve n2 parametreleriyle çalıştırılacaktır. Elde edilen n sonucu, en son yürütülen yöntemin sonucudur. Tüm yöntemlerin ürettiği sonuçları elde etmek mümkün değildir. Bu nedenle, bir delege fonksiyona bir yöntem listesi kaydedilirse, bu yöntemler çoğunlukla void türünde bir sonuç döndürür.

Aşağıdaki örneği ele alalım:


using System;
namespace Chap6 {
    class Class1 {
        // fonksiyon prototipi tanımlama
        // 2 tamsayı parametre alır ve bir tamsayı döndürür
        public delegate int Opération(int n1, int n2);

        // prototipe karşılık gelen iki örnekleme yöntemi
        public int Ajouter(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }//ekle

        public int Soustraire(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }// çıkarma

        // prototipe karşılık gelen bir statik yöntem
        public static int Augmenter(int n1, int n2) {
            Console.WriteLine("Augmenter(" + n1 + "," + n2 + ")");
            return n1 + 2 * n2;
        }//artır

        static void Main(string[] args) {

            // işlem türü bir nesne tanımlayarak fonksiyonları bu nesneye kaydediyoruz
            // "artır" statik fonksiyonu kaydedilir
            Opération op = Augmenter;
            // delegeyi çalıştırıyoruz
            int n = op(4, 7);
            Console.WriteLine("n=" + n);

            // class1 türünde bir c1 nesnesi oluşturulur
            Class1 c1 = new Class1();
            // c1 nesnesinin "ekle" yöntemini delegeye kaydedilir
            op = c1.Ajouter;
            // delege nesnesinin çalıştırılması
            n = op(2, 3);
            Console.WriteLine("n=" + n);
            // c1'in "çıkarma" yöntemini delegeye kaydediyoruz
            op = c1.Soustraire;
            n = op(2, 3);
            Console.WriteLine("n=" + n);
            //Delegeye iki fonksiyonun kaydedilmesi
            op = c1.Ajouter;
            op += c1.Soustraire;
            // delege nesnesinin çalıştırılması
            op(0, 0);
            // delege nesnesinden bir fonksiyonun kaldırılması
            op -= c1.Soustraire;
            // delege çalıştırılır
            op(1, 1);
        }
    }
}
  • 3. satır: Class1 sınıfını tanımlar.
  • 6. satır: delegate'in tanımı Opération: int türünde iki parametre alan ve int türünde bir sonuç döndüren yöntem prototipi
  • 9-12. satırlar: Ajouter örnek yöntemi, Opération delege imzasına sahiptir.
  • satır 14-17: Soustraire örnek yöntemi, Opération delege imzasına sahiptir.
  • 20-23. satırlar: Augmenter sınıf yöntemi, Opération delege imzasına sahiptir.
  • 25. satır: Main yöntemi yürütülür
  • 20. satır: op değişkeni, Opération delegat türündedir. Bu değişken, Opération delegat türünün imza yapısına sahip yöntemlerin bir listesini içerecektir. Bu değişkene, statik Class1.Augmenter yöntemine ait ilk yöntem referansı atanır.
  • 31. satır: delegate op yürütülür: op tarafından referans verilen tüm yöntemler yürütülecektir. Bu yöntemler, delegate ve op.'e aktarılan parametrelerle çalıştırılacaktır. Burada, yalnızca Class1.Augmenter statik yöntemi çalıştırılacaktır.
  • 35. satır: Class1 sınıfından bir c1 örneği oluşturulur.
  • 37. satır: c1.Ajouter örnek yöntemi, op delegatına atanır. Augmenter statik bir yöntemken, Ajouter bir örnek yöntemdir. Bunun bir önemi olmadığını göstermek istedik.
  • 39. satır: delegate op yürütülür: Ajouter yöntemi, delegate op'e aktarılan parametrelerle yürütülecektir.
  • 42. satır: Aynı işlem, Soustraire örnek yöntemi için de tekrarlanır.
  • 46-47. satırlar: Ajouter ve Soustraire yöntemleri, delegate ve op yöntemlerine eklenir.
  • 49. satır: delegate ve op yürütülür: Ajouter ve Soustraire yöntemlerinin her ikisi de, delegate ve op'e aktarılan parametrelerle çalıştırılacaktır.
  • 51. satır: Soustraire yöntemi, delegate ve op'ten kaldırılır.
  • 53. satır: delegate ve op yürütülür: kalan Ajouter yöntemi de yürütülecektir.

Yürütme sonuçları şunlardır:

1
2
3
4
5
6
7
8
9
Augmenter(4,7)
n=18
Ajouter(2,3)
n=5
Soustraire(2,3)
n=-1
Ajouter(0,0)
Soustraire(0,0)
Ajouter(1,1)

8.3. Delege mi, yoksa arayüz mü?

delegates ve arayüz kavramları birbirine oldukça yakın görünebilir ve bu iki kavram arasındaki farkların tam olarak ne olduğu merak edilebilir. Daha önce incelediğimiz bir örneğe benzer şu örneği ele alalım:


using System;
namespace Chap6 {
    class Program1 {
        // bir işlev prototipinin tanımlanması
        // 2 tamsayı parametre alır ve bir tamsayı döndürür
        public delegate int Opération(int n1, int n2);

        // prototipe karşılık gelen iki örnek yöntemi
        public static int Ajouter(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }//ekle

        public static int Soustraire(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }// çıkarma

        // Bir delege yürütülür
        public static int Execute(Opération op, int n1, int n2){
            return op(n1, n2);
        }

        static void Main(string[] args) {
            // Ekle delegesinin yürütülmesi
            Console.WriteLine(Execute(Ajouter, 2, 3));
            // delege yürütme Çıkarma
            Console.WriteLine(Execute(Soustraire, 2, 3));
            // bir çoklu yayın delege işlevinin yürütülmesi
            Opération op = Ajouter;
            op += Soustraire;
            Console.WriteLine(Execute(op, 2, 3));
            // delege işlevinden bir işlev çıkarılır
            op -= Soustraire;
            // delege çalıştırılıyor
            Console.WriteLine(Execute(op, 2, 3));
        }
    }
}
  1. satırda, Execute yöntemi, 6. satırda tanımlanan Opération delegat türünde bir nesneye referans beklemektedir. Bu, Execute yöntemine farklı yöntemlerin (26, 28, 32 ve 36. satırlar) aktarılmasını sağlar. Bu polimorfizm özelliği bir arayüz kullanılarak da elde edilebilir:

using System;

namespace Chap6 {

    // IOperation arayüzü
    public interface IOperation {
        int operation(int n1, int n2);
    }

    // Add sınıfı
    public class Ajouter : IOperation {
        public int operation(int n1, int n2) {
            Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
            return n1 + n2;
        }
    }

    // Çıkarma sınıfı
    public class Soustraire : IOperation {
        public int operation(int n1, int n2) {
            Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
            return n1 - n2;
        }
    }

    // test sınıfı
    public static class Program2 {
        // IOperation arayüzünün tek yönteminin yürütülmesi
        public static int Execute(IOperation op, int n1, int n2) {
            return op.operation(n1, n2);
        }

        public static void Main() {
            // Ekle delegesinin yürütülmesi
            Console.WriteLine(Execute(new Ajouter(), 2, 3));
            // Çıkarma delege yönteminin yürütülmesi
            Console.WriteLine(Execute(new Soustraire(), 2, 3));
        }
    }
}
  • 6-8. satırlar: [IOperation] arayüzü, operation yöntemini tanımlar.
  • satır 11-16 ve 19-24: [Ajouter] ve [Soustraire] sınıfları, [IOperation] arayüzünü uygular.
  • 29-31. satırlar: İlk parametresi IOperation arayüzünün türünde olan Execute yöntemi. Execute yöntemi, birinci parametre olarak sırasıyla Ajouter sınıfından bir örnek, ardından da Soustraire sınıfından bir örnek alacaktır.

Önceki örnekte delegate türündeki parametrede görülen polimorfik özellik burada da mevcuttur. Her iki örnek de aynı zamanda bu iki kavram arasındaki farkları göstermektedir.

delegate ve interface türleri,

  • . Aslında, delegate türü tek bir yöntem için bir sarmalayıcıdır; oysa arayüz birden fazla yöntem tanımlayabilir.
  • delegate'in çoklu yayın özelliği kullanılmıyorsa. Zaten arayüzde çoklu yayın kavramı mevcut değildir.

Bu iki koşul karşılanıyorsa, Execute yöntemi için aşağıdaki iki imza arasından seçim yapılabilir:


int Execute(IOperation op, int n1, int n2)
int Execute(Opération op, int n1, int n2)

delegate'i kullanan ikinci imza, kullanım açısından daha esnek olabilir. Zira ilk imzada, yöntemin ilk parametresi IOperation arayüzünü uygulamalıdır. Bu durum, Execute yöntemine ilk parametre olarak aktarılacak yöntemi tanımlamak üzere bir sınıf oluşturulmasını gerektirir. İkinci imza biçiminde ise, doğru imza biçimine sahip herhangi bir mevcut yöntem iş görür. Ek bir yapı oluşturmaya gerek yoktur.

8.4. Olay Yönetimi

delegate nesneleri, olayları tanımlamak için kullanılabilir. Bir C1 sınıfı, bir evt olayını şu şekilde tanımlayabilir:

  • delegate türü, C1 sınıfının içinde veya dışında tanımlanır:
delegate TResult Evt(T1 param1, T2 param2, ...);
  • C1 sınıfı, delegate Evt türünde bir alan tanımlar:
public Evt Evt1;
  • C1 sınıfının bir c1 örneği bir olayı bildirmek istediğinde, Evt delegasyonu tarafından tanımlanan parametreleri ona aktararak Evt1 delegasyonunu çalıştıracaktır. delegate ve Evt1 sınıflarında kayıtlı tüm yöntemler bu parametrelerle çalıştırılacaktır. Bu yöntemlerin Evt1 olayından haberdar oldukları söylenebilir.
  • c2 nesnesi, c1 nesnesini kullanarak c1 nesnesinde Evt1 olayının meydana geldiği konusunda bilgilendirilmek isterse, c1. nesnesinin c1.Evt1 adlı delege nesnesine c2.M adlı yöntemlerinden birini kaydedecektir. Böylece,c1 nesnesinde Evt1 olayı meydana geldiğinde bu yöntem çalıştırılacaktır. Ayrıca, olay hakkında artık bildirim almak istemediğinde kaydını silebilir.
  • Tıpkı c1.Evt1 delege nesnesinin birden fazla yöntem kaydedebilmesi gibi, farklı ci nesneleri, c1 üzerindeki Evt1 olayından haberdar olmak için c1.Evt1 delegesine kaydolabilir.

Bu senaryoda şunlar vardır:

  • bir olay bildiren bir sınıf
  • bu olaydan haberdar edilen sınıflar. Bunların olaya abone oldukları veya olaya kayıt oldukları söylenir.
  • olaydan haberdar edilecek yöntemlerin imzasını tanımlayan bir delegate türü

.NET çerçeve şunu tanımlar:

  • delegate olayının standart imzası
public delegate void MyEventHandler(object source, EventArgs evtInfo);
  • source: Olayı bildiren nesne
  • evtInfo: olay hakkında bilgi sağlayan, EventArgs türü veya bunun türevlerinden bir nesne
  • delegate adının sonu EventHandler ile bitmelidir
  • Bir sınıfta MyEventHandler türünde bir olayı bildirmek için standart bir yöntem:
1
2
3
4
public Class C1{
    public event MyEventHandler Evt1;
...
}

Evt1 alanı, delegate türündedir. event anahtar sözcüğü, bu alan üzerinde gerçekleştirilebilecek işlemleri kısıtlamak için kullanılmaktadır:

  • C1 sınıfının dışından, yalnızca += ve -= işlemleri yapılabilir. Bu, olaya abone olan yöntemlerin (örneğin geliştiricinin hatası nedeniyle) silinmesini engeller. Olaya abone olmak (+=) veya abonelikten çıkmak (-=) mümkündür.
  • Yalnızca C1 türünde bir örnek, Evt1. olayına abone olan yöntemlerin yürütülmesini tetikleyen Evt1(source,evtInfo) çağrısını gerçekleştirebilir

.NET çerçeve yapısı, bir olayın delegate imzasına uyan genel bir yöntem sağlar:

public delegate void EventHandler<TEventArgs>(object source, TEventArgs evtInfo) where TEventArgs : EventArgs
  • delegate ve EventHandler, ikinci parametresinin türü olan TEventArgs genel türünü kullanır
  • TEventArgs türü, EventsArgs türünden türetilmelidir (burada TEventArgs: EventArgs)

Bu genel delegate türüyle, C sınıfında bir X olayının tanımlanması aşağıdaki önerilen şemaya uygun olacaktır:

  • X olayına ilişkin bilgileri kapsüllemek için EventArgs'ten türetilmiş bir XEventArgs türü tanımlayın
  • C sınıfında EventHandler<XEventArgs> türünde bir olay tanımlayın.
  • C sınıfında korumalı bir yöntem tanımlayın
protected void OnXHandler(XEventArgs e);

tanımlayın; bu yöntem, X olayını abonelere "yayınlamak" için kullanılır.

Aşağıdaki örneği ele alalım:

  • Emetteur sınıfı bir sıcaklığı kapsüller. Bu sıcaklık izlenir. Bu sıcaklık belirli bir eşiği aştığında bir olay tetiklenmelidir. Bu olaya TemperatureTropHaute adını vereceğiz. Bu olayla ilgili bilgiler TemperatureTropHauteEventArgs türünde kapsüllenecektir.
  • Souscripteur sınıfı, önceki olaya abone olur. Olay hakkında bilgilendirildiğinde, konsolda bir mesaj görüntüler.
  • Bir konsol programı bir yayıncı ve iki abone oluşturur. Klavyeden sıcaklık değerlerini alır ve bunları bir Emetteur örneğine kaydeder. Sıcaklık çok yüksekse, Emetteur örneği TemperatureTropHaute olayını yayınlar.

Olay yönetimine ilişkin önerilen yönteme uymak için, öncelikle olay bilgilerini kapsüllemek üzere TemperatureTropHauteEventArgs türünü tanımlıyoruz:


using System;

namespace Chap6 {
    public class TemperatureTropHauteEventArgs:EventArgs {
        // olay sırasındaki sıcaklık
        public decimal Temperature { get; set; }
        // üreticiler
        public TemperatureTropHauteEventArgs() {
        }
        public TemperatureTropHauteEventArgs(decimal temperature) {
            Temperature = temperature;
        }
    }
}
  • 6. satır: TemperatureTropHauteEventArgs sınıfı tarafından kapsüllenmiş bilgi, TemperatureTropHaute olayını tetikleyen sıcaklıktır.

Emetteur sınıfı şu şekildedir:


using System;

namespace Chap6 {
    public class Emetteur {
        static decimal SEUIL = 19;

        // gözlemlenen sıcaklık
        private decimal temperature;
        // kaynak adı
        public string Nom { get; set; }
        // olay bildirildi
        public event EventHandler<TemperatureTropHauteEventArgs> TemperatureTropHaute;

        // sıcaklık okuma/yazma
        public decimal Temperature {
            get {
                return temperature;
            }
            set {
                temperature = value;
                if (temperature > SEUIL) {
                    // olay abonelere bildirilir
                    OnTemperatureTropHaute(new TemperatureTropHauteEventArgs(temperature));
                }
            }
        }

        // olay bildirimi
        protected virtual void OnTemperatureTropHaute(TemperatureTropHauteEventArgs evt) {
            // TemperatureTropHaute olayının abonelere iletilmesi
            TemperatureTropHaute(this, evt);
        }
    }
}
  • 5. satır: TemperatureTropHaute olayının yayınlanacağı sıcaklık eşiği.
  • 10. satır: Vericinin tanımlanması için bir adı vardır
  • 12. satır: TemperatureTropHaute olayı.
  • satır 15-26: Sıcaklığı veren get yöntemi ve bunu kaydeden set yöntemi. set yöntemi, kaydedilecek sıcaklık 5. satırdaki eşiği aşarsa TemperatureTropHaute olayını yayınlar. Bu yöntem, eşiği aşan sıcaklığın kaydedildiği bir TemperatureTropHauteEventArgs nesnesini parametre olarak geçirerek, 29. satırdaki OnTemperatureTropHauteHandler yöntemi aracılığıyla olayı yayınlar.
  • 29-32. satırlar: TemperatureTropHaute olayı, birinci parametre olarak vericinin kendisini ve ikinci parametre olarak parametre olarak alınan TemperatureTropHauteEventArgs nesnesini kullanarak yayınlanır.

TemperatureTropHaute olayına abone olacak Souscripteur sınıfı şöyledir:


using System;

namespace Chap6 {
    public class Souscripteur {
        // ad
        public string Nom { get; set; }

        // TemperatureTropHaute olay yöneticisi
        public void EvtTemperatureTropHaute(object source, TemperatureTropHauteEventArgs e) {
            // operatör konsolu ekranı
            Console.WriteLine("Souscripteur [{0}] : la source [{1}] a signalé une température trop haute : [{2}]", Nom, ((Emetteur)source).Nom, e.Temperature);
        }
    }
}
  • 6. satır: Her abone bir adla tanımlanır.
  • 9-12. satırlar: TemperatureTropHaute olayına eşleştirilecek yöntem. Bu yöntemin, bir olay yöneticisinin sahip olması gereken delegate EventHandler<TEventArgs> türünde bir imzası vardır. Yöntem, konsolda şunları görüntüler: mesajı görüntüleyen abonenin adı, olayı bildiren yayıncının adı ve olayı tetikleyen sıcaklık.
  • TemperatureTropHaute olayına, Emetteur nesnesinin aboneliği, Souscripteur sınıfında yapılmamaktadır. Bu, harici bir sınıf tarafından yapılacaktır.

[Program.cs] programı tüm bu öğeleri birbirine bağlar:


using System;
namespace Chap6 {
    class Program {
        static void Main(string[] args) {
            // olay vericisi oluşturma
            Emetteur e1 = new Emetteur() { Nom = "e" };
            // 2 aboneye sahip bir tablo oluşturma
            Souscripteur[] souscripteurs = new Souscripteur[2];
            for (int i = 0; i < souscripteurs.Length; i++) {
                // abone oluşturma
                souscripteurs[i] = new Souscripteur() { Nom = "s" + i };
                // e1'den TemperatureTropHaute olayına abone oluyor
                e1.TemperatureTropHaute += souscripteurs[i].EvtTemperatureTropHaute;
            }
            // klavyeden sıcaklıklar okunuyor
            decimal temperature;
            Console.Write("Température (rien pour arrêter) : ");
            string saisie = Console.ReadLine().Trim();
            // girdilen satır boş olmadığı sürece
            while (saisie != "") {
                // Girilen değer ondalık bir sayı mı?
                if (decimal.TryParse(saisie, out temperature)) {
                    // sıcaklık doğru - kaydedilir
                    e1.Temperature = temperature;
                } else {
                    // hata bildirilir
                    Console.WriteLine("Température incorrecte");
                }
                // yeni giriş
                Console.Write("Température (rien pour arrêter) : ");
                saisie = Console.ReadLine().Trim();
            }//while
        }
    }
}
  • 6. satır: yayıncının oluşturulması
  • 8-14. satırlar: Yayıcının TemperatureTropHaute olayına abone olacak iki abone oluşturulur.
  • 20-32. satırlar: klavyeden sıcaklık girişi döngüsü
  • 24. satır: Girilen sıcaklık doğruysa, Emetteur e1 nesnesine iletilir; sıcaklık 19 °C'den yüksekse bu nesne TemperatureTropHaute olayını tetikler.

Çalıştırma sonuçları şöyledir:

1
2
3
4
Température (rien pour arrêter) : 17
Température (rien pour arrêter) : 21
Souscripteur [s0] : la source [e] a signalé une température trop haute : [21]
Souscripteur [s1] : la source [e] a signalé une température trop haute : [21]