8. رویدادهای کاربر
در فصل قبلی، مفهوم رویدادهای مرتبط با اجزای فرم را بررسی کردیم. اکنون خواهیم دید چگونه رویدادها را در کلاسهای خود ایجاد کنیم.
8.1. ابژههای نمایندهٔ از پیش تعریفشده
مفهوم شیء delegate در فصل قبلی معرفی شد، اما در آن زمان تنها به طور مختصر به آن پرداخته شد. وقتی بررسی کردیم که چگونه رویدادپردازها برای اجزای فرم تعریف میشوند، با کدی مشابه زیر مواجه شدیم:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
که در آن buttonAfficher یک کامپوننت از نوع [Button] بود. این کلاس یک فیلد Click دارد که به صورت زیر تعریف شده است:
![]() |
- [1]: کلاس [Button]
- [2]: رویدادهای آن
- [3,4]: رویداد Click
- [5]: اعلان رویداد [Control.Click] [4].
- EventHandler یک پروتوتایپ متد (قالب) است که به عنوان یک دِلیگیت شناخته میشود.
- event یک کلمه کلیدی است که قابلیتهای delegate و EventHandler را محدود میکند: یک شیء delegate ویژگیهای بیشتری نسبت به یک شیء event دارد.
delegate EventHandler به شرح زیر تعریف شده است:
![]() |
delegate EventHandler به یک قالب متد اشاره دارد:
- که پارامتر اول آن از نوع Object است
- با پارامتر دوم از نوع EventArgs
- بدون بازگشت نتیجه
یک متد متناظر با قالب تعریفشده توسط EventHandler میتواند به شکل زیر باشد:
private void buttonAfficher_Click(object sender, EventArgs e);
برای ایجاد یک شی از نوع EventHandler، به شرح زیر عمل کنید:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
سپس میتوانید بنویسید:
یک متغیر از نوع delegate در واقع فهرستی از ارجاعات به متدهای مربوط به مدل delegate است. برای افزودن یک متد جدید M به متغیر evtHandler فوق، از سینتکس زیر استفاده کنید:
نشانهگذاری += حتی اگر evtHandler یک لیست خالی باشد، قابل استفاده است.
دستور:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
یک متد از نوع EventHandler به فهرست متدهای رویداد buttonAfficher.Click اضافه میکند. وقتی رویداد Click روی کامپوننت buttonAfficher رخ میدهد، VB دستور زیر را اجرا میکند:
که در آن:
- source جزئی از نوع object است که رویداد را فعال کرده است
- evt، که از نوع EventArgs است و حاوی هیچ اطلاعاتی نیست
تمام متدهای امضاشده void M(object,EventArgs) که توسط: با رویداد Click مرتبط شدهاند
this.buttonAfficher.Click += new System.EventHandler(M);
با پارامترهای (source, evt) که توسط VB ارسال شده است، فراخوانی خواهند شد.
8.2. تعریف اشیاء نماینده
دستور
public delegate int Opération(int n1, int n2);
یک نوع به نام pération را تعریف میکند که نمایانگر یک پیشنویس تابع است که دو عدد صحیح را به عنوان ورودی میگیرد و یک عدد صحیح را برمیگرداند. این کلمه کلیدی delegate است که Opération را به یک تعریف پیشنویس تابع تبدیل میکند.
یک متغیر op از نوع Opération برای ذخیره فهرستی از توابع متناظر با پیشنویس Opération استفاده خواهد شد:
یک متد fi با استفاده از op=new Operation(fi) در متغیر op ذخیره میشود. یا، به سادگی، با op=fi. برای افزودن یک متد fj به لیست توابع از پیش ذخیره شده، بنویسید op+= fj. برای حذف یک متد fk که قبلاً ذخیره شده است، بنویسید op-=fk. اگر در مثال ما n=op(n1,n2) را بنویسیم، تمام متدهای ذخیره شده در متغیر op با پارامترهای n1 و n2 اجرا خواهند شد. n حاصل، مربوط به آخرین متدی است که اجرا شده است. امکان بهدستآوردن نتایج تولیدشده توسط همه متدها وجود ندارد. به همین دلیل، اگر فهرستی از متدها در یک تابع نماینده ذخیره شود، این متدها معمولاً نتیجهای از نوع void برمیگردانند.
مثال زیر را در نظر بگیرید:
using System;
namespace Chap6 {
class Class1 {
// تعریف یک پروتوتیپ تابع
//دو عدد صحیح را بهعنوان پارامتر میپذیرد و یک عدد صحیح بازمیگرداند
public delegate int Opération(int n1, int n2);
// دو متد نمونه که به پروتوتایپ مربوط میشوند
public int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//جمع
public int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//تفریق
//یک متد ایستا متناظر با پروتوتایپ
public static int Augmenter(int n1, int n2) {
Console.WriteLine("Augmenter(" + n1 + "," + n2 + ")");
return n1 + 2 * n2;
}//افزایش
static void Main(string[] args) {
//یک شیء از نوع 'operation' برای نگهداری توابع تعریف میشود
// تابع ایستا 'افزایش' ثبت میشود
Opération op = Augmenter;
// دیلِگیت اجرا میشود
int n = op(4, 7);
Console.WriteLine("n=" + n);
// یک شیء c1 از نوع class1 ایجاد کنید
Class1 c1 = new Class1();
// متد 'add' از c1 در دِلیگیت ثبت میشود
op = c1.Ajouter;
// اجرای شیء دلیگیت
n = op(2, 3);
Console.WriteLine("n=" + n);
// متد 'subtract' از c1 در دلهی ثبت میشود
op = c1.Soustraire;
n = op(2, 3);
Console.WriteLine("n=" + n);
//ثبت دو تابع در دلیگیت
op = c1.Ajouter;
op += c1.Soustraire;
//اجرای شی دلیگیت
op(0, 0);
// حذف یک تابع از دلیگیت
op -= c1.Soustraire;
//اجرای دلیگیت
op(1, 1);
}
}
}
- خط ۳: کلاس Class1 را تعریف میکند.
- خط ۶: تعریف delegate Opération: یک نمونه اولیه از متدهایی که دو پارامتر از نوع int را میپذیرند و نتیجهای از نوع int برمیگردانند.
- خطوط ۹–۱۲: متد نمونه Ajouter دارای امضای دلیگیت Operation است.
- خطوط 14–17: متد نمونه Soustraire دارای امضای دلیگیت Operation است.
- خطوط ۲۰–۲۳: متد کلاسی Augmenter دارای امضای دلیگیت Operation است.
- خط ۲۵: متد Main اجرا میشود
- خط ۲۰: متغیر op از نوع Operation delegate است. این متغیر شامل فهرستی از متدها با امضای نوع Operation delegate خواهد بود. برای آن یک مرجع متد اول، یعنی متد استاتیک Class1.Augmenter، تخصیص داده شده است.
- خط ۳۱: delegate op اجرا میشود: تمام متدهایی که توسط op ارجاع شدهاند اجرا خواهند شد. آنها با استفاده از پارامترهای ارسالشده به delegate و op. اجرا خواهند شد. در اینجا، تنها متد استاتیک Class1.Augmenter اجرا خواهد شد.
- خط ۳۵: یک نمونه از کلاس Class1 با نام c1 ایجاد میشود.
- خط ۳۷: متد نمونه c1.Ajouter به دِلیگیت op اختصاص داده میشود. Augmenter یک متد استاتیک بود، Ajouter یک متد نمونه است. هدف نشان دادن این بود که این موضوع تفاوتی ایجاد نمیکند.
- خط ۳۹: delegate op اجرا میشود: متد Ajouter با پارامترهای ارسالشده به delegate op اجرا خواهد شد.
- خط ۴۲: همین فرایند با متد نمونه Soustraire تکرار میشود.
- خطوط ۴۶–۴۷: متدهای Ajouter و Soustraire درون delegate و op قرار میگیرند.
- خط ۴۹: delegate op اجرا میشود: دو متد Ajouter و Soustraire با پارامترهای ارسالشده به delegate و op اجرا خواهند شد.
- خط ۵۱: متد Soustraire از delegate و op حذف میشود.
- خط ۵۳: متدهای delegate و op اجرا میشوند: متد باقیمانده، Ajouter، اجرا خواهد شد.
نتایج اجرای به شرح زیر است:
8.3. نمایندهها یا رابطها؟
مفاهیم delegates و رابطها ممکن است بسیار شبیه به نظر برسند و ممکن است کسی دقیقاً بپرسد تفاوت این دو مفهوم چیست. بیایید مثال زیر را در نظر بگیریم که مشابه مثالی است که قبلاً بررسی کردهایم:
using System;
namespace Chap6 {
class Program1 {
// تعریف یک پیشپروتوتایپ تابع
// دو عدد صحیح را بهعنوان پارامتر میپذیرد و یک عدد صحیح بازمیگرداند
public delegate int Opération(int n1, int n2);
// دو متد نمونه متناظر با پروتوتایپ
public static int Ajouter(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}//اضافه
public static int Soustraire(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}//تفریق
//اجرای یک دلیگیت
public static int Execute(Opération op, int n1, int n2){
return op(n1, n2);
}
static void Main(string[] args) {
//اجرای دلیگیت «جمع»
Console.WriteLine(Execute(Ajouter, 2, 3));
//اجرای دلیگیت «تفریق»
Console.WriteLine(Execute(Soustraire, 2, 3));
//اجرای یک دلیگیت چندپخشی
Opération op = Ajouter;
op += Soustraire;
Console.WriteLine(Execute(op, 2, 3));
// حذف یک تابع از دلیگیت
op -= Soustraire;
// اجرای نماینده
Console.WriteLine(Execute(op, 2, 3));
}
}
}
در خط ۲۰، متد Execute منتظر یک مرجع به یک شی از نوع «دیلگییت Operation» است که در خط ۶ تعریف شده است. این امکان را میدهد که متدهای مختلف (خطوط ۲۶، ۲۸، ۳۲ و ۳۶) به متد Execute پاس داده شوند. این چندریختی را میتوان با استفاده از یک رابط نیز پیادهسازی کرد:
using System;
namespace Chap6 {
// رابط IOperation
public interface IOperation {
int operation(int n1, int n2);
}
// افزودن کلاس
public class Ajouter : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Ajouter(" + n1 + "," + n2 + ")");
return n1 + n2;
}
}
// کسر کلاس
public class Soustraire : IOperation {
public int operation(int n1, int n2) {
Console.WriteLine("Soustraire(" + n1 + "," + n2 + ")");
return n1 - n2;
}
}
// کلاس تست
public static class Program2 {
//اجرای متد واحد رابط IOperation
public static int Execute(IOperation op, int n1, int n2) {
return op.operation(n1, n2);
}
public static void Main() {
//اجرای دلیگیت Add
Console.WriteLine(Execute(new Ajouter(), 2, 3));
//اجرای دلیگیت تفریق
Console.WriteLine(Execute(new Soustraire(), 2, 3));
}
}
}
- خطوط ۶–۸: رابط [IOperation] یک متد operation را تعریف میکند.
- خطوط ۱۱–۱۶ و ۱۹–۲۴: کلاسهای [Ajouter] و [Soustraire] رابط [IOperation] را پیادهسازی میکنند.
- خطوط ۲۹–۳۱: متد Execute که پارامتر اول آن از نوع تعریفشده توسط رابط IOperation است. متد Execute بهطور متوالی بهعنوان اولین پارامتر خود یک نمونه از کلاس Ajouter و سپس یک نمونه از کلاس Soustraire دریافت خواهد کرد.
این به وضوح ماهیت چندریختی پارامتر از نوع delegate را از مثال قبلی نشان میدهد. در عین حال، این دو مثال تفاوتهای بین این دو مفهوم را برجسته میکنند.
انواع delegate و interface قابل تعویض هستند
- اگر اینترفیس تنها یک متد داشته باشد. در واقع، نوع delegate یک کپسول (wrapper) برای یک متد واحد است، در حالی که خود اینترفیس میتواند چندین متد را تعریف کند.
- اگر جنبه چندپخشی delegate استفاده نشود. این مفهوم چندپخشی در واقع در اینفترفیس وجود ندارد.
اگر هر دو این شرطها برقرار باشند، در آن صورت بین دو امضای زیر برای متد Execute انتخابی وجود دارد:
int Execute(IOperation op, int n1, int n2)
int Execute(Opération op, int n1, int n2)
دومی که از delegate استفاده میکند، ممکن است انعطافپذیرتر باشد. این به این دلیل است که در امضای اول، اولین پارامتر متد باید رابط IOperation را پیادهسازی کند. این امر مستلزم ایجاد یک کلاس برای تعریف متدی است که باید به عنوان اولین پارامتر به متد Execute ارسال شود. در امضای دوم، هر متد موجود با امضای صحیح کافی است. نیازی به نوشتن کد اضافی نیست.
8.4. پردازش رویداد
ابجکتهای delegate میتوانند برای تعریف رویدادها استفاده شوند. یک کلاس C1 میتواند یک رویداد evt را به صورت زیر تعریف کند:
- یک نوع delegate درون یا برون کلاس C1 تعریف میشود:
- کلاس C1 یک فیلد از نوع delegate Evt را تعریف میکند:
- زمانی که یک نمونه از کلاس C1 قصد دارد یک رویداد را سیگنال دهد، دلیگیت Evt1 خود را اجرا کرده و پارامترهای تعریفشده توسط دلیگیت Evt را به آن پاس میدهد. سپس تمام متدهای ثبتشده در delegate و Evt1 با این پارامترها اجرا خواهند شد. میتوان گفت که آنها از رویداد Evt1 مطلع شدهاند.
- اگر یک شیء c2 که از یک شیء c1 استفاده میکند، بخواهد از وقوع رویداد Evt1 روی شیء c1 مطلع شود، یکی از متدهای خود، c2.M، را با شی نماینده c1.Evt1 از شی c1. ثبت میکند. بدین ترتیب، متد آن c2.M هر بار کهرویداد Evt1 روی شیء c1 رخ میدهد. همچنین میتواند زمانی که دیگر نمیخواهد از این رویداد مطلع شود، اشتراک خود را لغو کند.
- از آنجا که شیء نماینده c1.Evt1 میتواند چندین متد را ثبت کند، ابجکتهای ci مختلف میتوانند در دِلیگیت c1.Evt1 ثبتنام کنند تا از رویداد Evt1 روی c1 مطلع شوند.
در این سناریو، داریم:
- یک کلاس که یک رویداد را سیگنال میدهد
- کلاسهایی که از این رویداد مطلع میشوند. به این کلاسها گفته میشود که به رویداد «مشترک» شدهاند.
- یک نوع delegate که امضای متدهایی را که از رویداد مطلع خواهند شد، تعریف میکند
چارچوب .NET تعریف میکند:
- یک امضای استاندارد برای رویداد delegate
- source: شیئی که رویداد را گزارش کرده است
- evtInfo: یک شیء از نوع EventArgs یا یک نوع مشتق که اطلاعاتی درباره رویداد ارائه میدهد
- نام delegate باید با EventHandler پایان یابد
- یک روش استاندارد برای اعلام یک رویداد از نوع MyEventHandler در یک کلاس:
میدان Evt1 از نوع delegate است. کلمه کلیدی event برای محدود کردن عملیات قابل انجام بر روی آن وجود دارد:
- از خارج از کلاس C1، تنها عملیاتهای += و -= مجاز هستند. این کار از حذف (مثلاً به دلیل خطای توسعهدهنده) متدهایی که به رویداد مشترک شدهاند جلوگیری میکند. میتوان به سادگی به رویداد مشترک شد (+=) یا اشتراک را لغو کرد (-=).
- تنها یک نمونه از نوع C1 میتواند فراخوانی Evt1(source, evtInfo) را اجرا کند، که باعث اجرای متدهای مشترک در رویداد Evt1. میشود.
چارچوب .NET یک متد عمومی را فراهم میکند که با امضای delegate یک رویداد مطابقت دارد:
public delegate void EventHandler<TEventArgs>(object source, TEventArgs evtInfo) where TEventArgs : EventArgs
- delegate از نوع عمومی TEventArgs استفاده میکند که نوع پارامتر دوم آن است
- نوع TEventArgs باید مشتق شده از نوع EventsArgs باشد (که در آن TEventArgs : EventArgs)
با این نوع عمومی delegate، اعلان یک رویداد X در کلاس C از ساختار توصیهشده زیر پیروی خواهد کرد:
- یک نوع XEventArgs را مشتقشده از EventArgs تعریف کنید تا اطلاعات مربوط به رویداد X را در بر گیرد
- در کلاس C، یک رویداد از نوع EventHandler<XEventArgs> تعریف کنید.
- یک متد محافظتشده در کلاس C تعریف کنید
منظور «انتشار» رویداد X برای مشترکین است.
مثال زیر را در نظر بگیرید:
- کلاسی به نام Emetteur یک دما را در بر میگیرد. این دما پایش میشود. هنگامی که این دما از یک آستانه مشخص فراتر رود، باید یک رویداد فعال شود. ما این رویداد را TemperatureTropHaute مینامیم. اطلاعات مربوط به این رویداد در نوع TemperatureTropHauteEventArgs در بر گرفته خواهد شد.
- کلاسی به نام Souscripteur به رویداد مذکور مشترک میشود. هنگامی که از رویداد مطلع میشود، پیامی را روی کنسول نمایش میدهد.
- یک برنامه کنسول یک ناشر و دو مشترک ایجاد میکند. دماها را از طریق صفحهکلید وارد کرده و آنها را در یک نمونه از نوع Emetteur ذخیره میکند. اگر دما بیش از حد بالا باشد، نمونه Emetteur رویداد TemperatureTropHaute را منتشر میکند.
برای پیروی از روش توصیهشده برای مدیریت رویدادها، ابتدا نوع TemperatureTropHauteEventArgs را برای دربرگرفتن اطلاعات رویداد تعریف میکنیم:
using System;
namespace Chap6 {
public class TemperatureTropHauteEventArgs:EventArgs {
// دما در زمان وقوع رویداد
public decimal Temperature { get; set; }
// سازندگان
public TemperatureTropHauteEventArgs() {
}
public TemperatureTropHauteEventArgs(decimal temperature) {
Temperature = temperature;
}
}
}
- خط ۶: اطلاعاتی که توسط کلاس TemperatureTropHauteEventArgs در بر گرفته شده است، دمایی است که رویداد TemperatureTropHaute را فعال کرده است.
کلاس Emetteur به شرح زیر است:
using System;
namespace Chap6 {
public class Emetteur {
static decimal SEUIL = 19;
// دمای مشاهدهشده
private decimal temperature;
// نام منبع
public string Nom { get; set; }
// رویداد گزارششده
public event EventHandler<TemperatureTropHauteEventArgs> TemperatureTropHaute;
// دمای خواندن/نوشتن
public decimal Temperature {
get {
return temperature;
}
set {
temperature = value;
if (temperature > SEUIL) {
// رویدادی که به مشترکین گزارش شده است
OnTemperatureTropHaute(new TemperatureTropHauteEventArgs(temperature));
}
}
}
// اطلاعرسانی رویداد
protected virtual void OnTemperatureTropHaute(TemperatureTropHauteEventArgs evt) {
//رویداد TemperatureTropHaute برای مشترکین ارسال شد
TemperatureTropHaute(this, evt);
}
}
}
- خط ۵: آستانهی دمایی که رویداد TemperatureTropHaute بالاتر از آن منتشر خواهد شد.
- خط ۱۰: فرستنده برای اهداف شناسایی نام دارد
- خط ۱۲: رویداد TemperatureTropHaute.
- خطوط ۱۵–۲۶: متد get که دما را بازمیگرداند و متد set که آن را ثبت میکند. این متد set است که در صورتی که دمای قابل ثبت از آستانهای که در خط ۵ مشخص شده فراتر رود، انتشار رویداد TemperatureTropHaute را تحریک میکند. این روش انتشار رویداد را از طریق متد OnTemperatureTropHauteHandler در خط ۲۹ راهاندازی میکند و یک شیء TemperatureTropHauteEventArgs را بهعنوان پارامتر به آن میفرستد که در آن دمای فراتر از آستانه ثبت شده است.
- خطوط ۲۹–۳۲: رویداد TemperatureTropHaute با ارسالکننده خود بهعنوان پارامتر اول و شیء دریافتی TemperatureTropHauteEventArgs بهعنوان پارامتر دوم منتشر میشود.
کلاس Souscripteur که به رویداد TemperatureTropHaute مشترک خواهد شد، به شرح زیر است:
using System;
namespace Chap6 {
public class Souscripteur {
// نام
public string Nom { get; set; }
// مدیر رویداد برای TemperatureTropHaute
public void EvtTemperatureTropHaute(object source, TemperatureTropHauteEventArgs e) {
//نمایش کنسول اپراتور
Console.WriteLine("Souscripteur [{0}] : la source [{1}] a signalé une température trop haute : [{2}]", Nom, ((Emetteur)source).Nom, e.Temperature);
}
}
}
- خط ۶: هر مشترک با یک نام شناسایی میشود.
- خطوط ۹–۱۲: متدی که با رویداد TemperatureTropHaute مرتبط خواهد شد. این متد دارای امضای نوع delegate EventHandler<TEventArgs> است که یک رویدادپرداز باید داشته باشد. این متد موارد زیر را در کنسول نمایش میدهد: نام مشترک که پیام را نمایش میدهد، نام ناشر که رویداد را گزارش کرده است و دمایی که آن را فعال کرده است.
- اشتراک برای رویداد TemperatureTropHaute برای یک شیء Emetteur در کلاس Souscripteur تنظیم نشده است. این اشتراک توسط یک کلاس خارجی تنظیم خواهد شد.
برنامه [Program.cs] همه این عناصر را به هم متصل میکند:
using System;
namespace Chap6 {
class Program {
static void Main(string[] args) {
// ایجاد فرستنده رویداد
Emetteur e1 = new Emetteur() { Nom = "e" };
// ایجاد آرایهای از ۲ مشترک
Souscripteur[] souscripteurs = new Souscripteur[2];
for (int i = 0; i < souscripteurs.Length; i++) {
// ایجاد یک مشترک
souscripteurs[i] = new Souscripteur() { Nom = "s" + i };
// مشترک شدن آن به رویداد TemperatureTropHaute از e1
e1.TemperatureTropHaute += souscripteurs[i].EvtTemperatureTropHaute;
}
//دماها از طریق صفحهکلید وارد میشوند
decimal temperature;
Console.Write("Température (rien pour arrêter) : ");
string saisie = Console.ReadLine().Trim();
// تا زمانی که خط واردشده خالی نباشد
while (saisie != "") {
//آیا ورودی یک عدد اعشاری است؟
if (decimal.TryParse(saisie, out temperature)) {
// دمای صحیح – ثبت میشود
e1.Temperature = temperature;
} else {
// گزارش خطا
Console.WriteLine("Température incorrecte");
}
// ورودی جدید
Console.Write("Température (rien pour arrêter) : ");
saisie = Console.ReadLine().Trim();
}//در حالی که
}
}
}
- خط ۶: ایجاد ناشر
- خطوط ۸–۱۴: ایجاد دو مشترک که به رویداد TemperatureTropHaute ناشر مشترک شدهاند.
- خطوط ۲۰–۳۲: حلقه برای وارد کردن دما از طریق صفحهکلید
- خط ۲۴: اگر دمای وارد شده صحیح باشد، به شیء e1 در Emetteur ارسال میشود که در صورت بالاتر بودن دما از ۱۹ درجه سانتیگراد، رویداد TemperatureTropHaute را فعال میکند.
نتایج اجرای برنامه به شرح زیر است:

