الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تنفيد الأحداث في لغة سي++

بدأه محمد رضى في 2 أغسطس 2009 · 4 رد · 2,616 مشاهدة · في المواضيع والدروس
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم و رحمة الله تعالى و بركاته,,

في هذا الموضوع, ستنتحدث, إن شاء الله, عن تنفيد الأحداث في لغة سي++. سنعرف الأحداث على أنها وسيلة تمكن عناصر سي++ من إعلام عناصر أخرى وقت حدوث حدث ما. الحدث يمكن أن يكون تغيير في محتوى متغير, إنشاء عنصر, تدميره, إلخ.

هذه لائحة متمنياتي :

- أريد من الحدث أن يكون قادرا على إشعار أي عنصر من العناصر.

- العنصر المرسل ليس مرغما على المعرفة المسبقة للعنصر المستقبل.

- العنصر المرسل يحدد شكل الدالة التي ستتم المناداة عليها لإشعار العنصر المستقبل بوقوع الحدث.

- العنصر المرسل يحدد نوع العنصر الذي سيتم تمريره للدالة المستقبلة و هذا العنصر سيحتوي كل المعلومات المتعلقة بالحدث.

- سنكتفي في هذه المحاولة الأولى بإمكانية إشتراك واحد في الحدث.

ليتمكن المرسل من إشعار المستقبل أي مستقبل بالحدث, المرسل لا يجب أن يكون مرغما بمعرفة المستقبل و لهذا سنعتمد على كلاس افتراضي يمكن من تسجيل اشتراك في الحدث و الإشعار به دون معرفة مسبقة بالمستقبل :

// كلاس إفتراضية تستعمل كمؤشر لكلاس event 
class IEvent
{
public :
	// تقوم هذه الدالة بالمناداة على الدالة التي تم تسجيلها على أنها المستقبل للإشعار بحدوث الحدث
	virtual void Invoke(ISender *pSender, IEventArgs *pEventArgs) = 0;
};

الكلاس event في شكلها الحالي صغيرة و بسيطة جدا و سيتم تطويرها في الردود التالية :

// كلاس لتسجيل و تسيير حدث
// نوع الكلاس التي ستستقبل الإشعار بحدوث الحدث R
// نوع الدالة التي سيتم المناداة عليها وقت حدوث الحدث و الإشعار به F
template<class R, class F>
class event : public IEvent
{
private :
	// العنصر الذي سيستقبل الإشعار بالحدث
	R *m_pReceiver;
	// مؤشر نحو الدالة الموجودة بالعنصر المستقبل و التي سيتم المناداة عليها لإشعار العنصر بحدوث الحدث
	F m_pEventFunc;
public :
	// تهيئة الحدث
	// مؤشر العنصر المستقبل للحدث : pReceiver
	// مؤشر الدالة التي سيتم المناداة عليها وقت حدوث الحدث : pEvFunc
	event(R *pReceiver, F pEvFunc) : m_pReceiver(pReceiver), m_pEventFunc(pEvFunc) 

{}

	// IEvent تفعيل للدالة الموروثة عن الكلاس 
	// تقوم هذه الدالة بالمناداة على الدالة التي تم تسجيلها على أنها المستقبل للإشعار بحدوث الحدث
	void Invoke(ISender *pSender, IEventArgs *pEventArgs)
	{
		(m_pReceiver->*m_pEventFunc)(pSender, pEventArgs);
	}
};

في نسختها الحالية الكلاس event تقوم :

event(R *pReceiver, F pEvFunc);

الباني الإفتراضي للكلاس event والذي يقوك بتسجيل المعلومات الازمة لإشعار عنصر عند وقوع حدث ما. pReceiver : مؤشر العنصر المستقبل للإشعار بوقوع الحدث. pEvFunc : مؤشر الدالة التي سيتم المناداة عنها للإشعار بوقوع الحدث.

void Invoke(ISender *pSender, IEventArgs *pEventArgs);

الدالة التي سيستعملها العنصر المرسل للمناداة على الدالة التي ستستقبل الإشعار بوقوع الحدث. pSender : مؤشر العنصر الذي أرسل الإشعار بوقوع الحدث. pEventArgs : العنصر الوريث للكلاس الإفتراضية IEventArgs و الذي سيحتوي على معلومات -يحددها العنصر المرسل- عن الحدث.

الكلاس IEventArgs ستكون, إن شاء الله, الكلاس الأم لكل كلاس مشخصة لحمل معلومات متعلقة بحدث ما.

الكلاس المرسل هو الذي يحدد نوع الدالة التي يجب على المستقبل تفعيلها لاستقبال الإشعار

بالحدث. و النوع العادي لهذه الدالة هو على الشكل التالي :

void event_function_name(ISender *pSender, IEventArgs *pEventArgs)

أنظر للدالة IEvent::Invoke للمزيد من المعلومات.

وفي الأخير سنتحدث عن طريقة تعريف و تهيئة الحدث :

أولا سنعرف مؤشر للكلاس IEvent على الكلاس المرسل :

IEvent *pEvent_Name;

ثانيا سنعرف و نفعل الدالة المستقبلة للإشعار بوقوع الحدث داخل الكلاس المستقبلة للحدث :

	void event_function_name(ISender *pSender, IEventArgs *pEventArgs)
	{
		// الكود الذي سيتم تنفيده عند الإشعار بوقوع الحدث
	}

ثالثا حجز اشتراك في الحدث :

pEvent_Name = (IEvent *)new event<Receiver_Class, P_STD_EVENT_FUNC>(&Receiver_Object, &Receiver_Class::event_function_name);

و أخيرا يمكننا المناداة على الدالة المستقبلة للإشعار بوقوع الحدث بالشكل التالي :

pEvent_Name->Invoke(pSende_Object, pEvent_Args_Object);

في الأيام القادمة سأنشر نسخة مطورة لهذه الكلاس والتي سيتم زيادة إمكانية تعدد الإشتراكات في الحدث الواحد من طرف العديد من العناصر إضافة إلى زيادة في حماية تسيير الأحداث.

المرجو التبليغ عن أي خطإ لأنه لم تتم تجربة هذه الكلاس...

في الملف المرفق ستجدون مشروعا عليه كل الكود إضافة لبرنامج صغير لتجربة الكلاس event

تحياتي...

event.rar

تم تعديل هذه المشاركة بواسطة محمد رضى في 2 أغسطس 2009 في 02:44

2
#2

السلام عليكم و رحمة الله تعالى و بركاته,,,

هذه الكلاس IEvent في حلتها الجديدة :

// كلاس إفتراضية تستعمل كمؤشر لكلاس event 
class IEvent
{
public :
	// تقوم هذه الدالة بالمناداة على الدالة التي تم تسجيلها على أنها المستقبل للإشعار بحدوث الحدث
	virtual void Invoke(ISender *pSender, IEventArgs *pEventArgs) = 0;
	// EventManager حتى يتم التنظيف في أحسن الحالات عند تدمير كائنات
	virtual ~IEvent(void) = 0 {};
};

حيث تمت زيادة مدمر افتراضى حتى تتم عملية تنظيف الكائن event في أفظل الأحوال.

هذه الكلاس event في حلتها الجديدة :

// كلاس لتسجيل و تسيير حدث
// نوع الكلاس التي ستستقبل الإشعار بحدوث الحدث R
// نوع الدالة التي سيتم المناداة عليها وقت حدوث الحدث و الإشعار به F
template<class R, class F>
class event : public IEvent
{
private :
	// العنصر الذي سيستقبل الإشعار بالحدث
	R *m_pReceiver;
	// مؤشر نحو الدالة الموجودة بالعنصر المستقبل و التي سيتم المناداة عليها لإشعار العنصر بحدوث الحدث
	F m_pEventFunc;
public :
	// تهيئة الحدث
	// مؤشر العنصر المستقبل للحدث : pReceiver
	// مؤشر الدالة التي سيتم المناداة عليها وقت حدوث الحدث : pEvFunc
	event(R *pReceiver, F pEvFunc) : m_pReceiver(pReceiver), m_pEventFunc(pEvFunc) {}

	// في حالة توفر كائنات تتطلب إزالتها في المدمر
	~event(void) {}

	// IEvent تفعيل للدالة الموروثة عن الكلاس 
	// تقوم هذه الدالة بالمناداة على الدالة التي تم تسجيلها على أنها المستقبل للإشعار بحدوث الحدث
	void Invoke(ISender *pSender, IEventArgs *pEventArgs)
	{
		(m_pReceiver->*m_pEventFunc)(pSender, pEventArgs);
	}

	// للتأكد من أن محتوى معلومات الحدث يساوي العنصر و الدالة
	template<class R, class F>
	bool Equals(R *pReceiver, F pEvFunc)
	{
		return (pReceiver == m_pReceiver) && (pEvFunc == m_pEventFunc);
	}
};

تمت زيادة تفعيل المدمر الإفتراضي للكلاس و لكن حتى الآن event مازالت بسيطة جدا و لا تحتاج لمدمر. تمت زيادة الدالة Equals التي تستقبل مؤشر كائن و مؤشر دالة و ترجع

لنا إذا كان الكائن event هو الحدث الذي تشتركان فيه أو لا. سنحتاج لهذه الدالة في حالة ما إذا أردنا سحب اشتراك عنصر من حدث ما.

و الأهم هو زيادة الكلاس EventManager التي ستتكلف بتسيير الإشتراكات لحدث ما :

//كلاس مسير الأحداث يتكلف بقبول و إلغاء الإشتراكات في الحدث المسير 
// ويقوم عند الحاجة بإعلام المشتركين بوقوع الحدث
class EventManager
{
private :
	static const int SIZE_CHUNK = 8;
private :
	IEvent **m_pIEArray;
	int m_iSize;
	int m_iCount;
private :
	void CheckInternalArray(void)
	{
		// التهيئة الأولى
		if(m_pIEArray == NULL) {
			m_pIEArray = (IEvent **)malloc(sizeof(IEvent *) * SIZE_CHUNK);
			if(m_pIEArray == NULL) throw "Run out of memory...";
			m_iSize += SIZE_CHUNK;
			for(int i = 0; i < m_iSize; i++) {
				m_pIEArray = NULL;
			}
			return;
		}
		// الحاجة لأماكن جديدة لتسجيل اشتراكات أخرى
		if(m_iCount >= m_iSize) {
			int oldSize = m_iSize;
			m_pIEArray = (IEvent **)realloc(m_pIEArray, sizeof(IEvent *) * SIZE_CHUNK + m_iSize);
			if(m_pIEArray == NULL) throw "Run out of memory...";
			m_iSize += SIZE_CHUNK;
			for(int i = oldSize; i < m_iSize; i++) {
				m_pIEArray = NULL;
			}
			return;
		}
		// TODO +> Free extra memory chunks if m_iSize - m_Count > SIZE_CHUNK
	}
public :
	// تهيئة مسير الأحداث
	EventManager(void) : m_pIEArray(NULL), m_iCount(0), m_iSize(0)
	{
		CheckInternalArray();
	}

	// تنظيف المكان قبل الخروج
	~EventManager(void)
	{
		if(m_iCount > 0) {
			for(int i = 0; i < m_iSize; i++) {
				if(m_pIEArray != NULL) {
					delete m_pIEArray;
				}
			}
		}
		if(m_pIEArray != NULL) {
			free(m_pIEArray);
		}
	}

	// إرجاع عدد الإشتراكات
	int GetCount(void) { return m_iCount; }

	// تسجيل اشتراك جديد لعنصر و الدالة المستقبلة لإشعار وقوع الحدث
	template<class R, class F>
	void AddEventHandler(R *pReceiver, F pEvFunc)
	{
		if(pReceiver == NULL) throw "Argument exception : pReceiver!";
		if(pEvFunc == NULL) throw "Argument exception : pEvFunc!";
		if(m_iCount >= m_iSize) CheckInternalArray();
		for(int i = 0; i < m_iSize; i++) {
			if(m_pIEArray == NULL) {
				m_pIEArray = (IEvent *)new event<R, F>(pReceiver, pEvFunc);
				m_iCount++;
				return;
			}
		}
	}

	// إلغاء اشتراك عنصر والدالة المستقبلة لإشعار وقوع الحدث
	template<class R, class F>
	void RemoveEventHandler(R *pReceiver, F pEvFunc)
	{
		if(pReceiver == NULL) throw "Argument exception : pReceiver!";
		if(pEvFunc == NULL) throw "Argument exception : pEvFunc!";
		if((m_iSize - m_iCount) >= SIZE_CHUNK) CheckInternalArray();
		for(int i = 0; i < m_iSize; i++) {
			if(m_pIEArray != NULL) {
				event<R, F> *pEvent = (event<R, F> *)m_pIEArray;
				if(pEvent->Equals(pReceiver, pEvFunc)) {
					delete pEvent;
					m_pIEArray = NULL;
					m_iCount--;
					return;
				}
			}
		}
	}

	// إعلام كل المشتركين في أن الحدث قد وقع
	void Invoke(ISender *pSender, IEventArgs *pEventArgs)
	{
		int j = 0;
		for(int i = 0; i < m_iSize; i++) {
			if(m_pIEArray != NULL) {
				m_pIEArray->Invoke(pSender, pEventArgs);
				if(++j >= m_iCount) return; // لعدم تضييع الوقت
			}
		}
	}
};

template<class R, class F>void AddEventHandler(R *pReceiver, F pEvFunc)

تقوم بتسجيل اشتراك لعنصر مستقبل و دالة الإشعار بحدوث الحدث.

void CheckInternalArray(void)

تقوم بتفقد المصفوفة التي يتم تسجيل الإشتراكات عليها.

int GetCount(void)

تقوم بإرجاع عدد الإشتراكات الحالية للحدث.

void Invoke(ISender *pSender, IEventArgs *pEventArgs)

تقوم بإعلام كل المشتركين بوقوع الحدث.

template<class R, class F>void RemoveEventHandler(R *pReceiver, F pEvFunc)

تقوم بإلغاء اشتراك كائن و دالته المستقبلة للإشعار بوقوع الحدث.

الآن أصبحت عملية تعريف و تهيئة الأحداث أسهل مما سبق :

أولا, نقوم بتعريف مسير الحدث في الكلاس المرسلة للحدث :

EventManager Event_Name;

ثانيا, نقوم بزيادة اشتراك لعنصر مستقبل و الدالة التي تتسلم الإشعار بوقوع الحدث :

Event_Name.AddEventHandler(&Class_Object, &Class_Name::Event_Function_Name);

ثالثا, نقوم بتعريف و تفعيل الدالة التي ستتسلم الإشعار بوقوع الحدث في الكلاس المستقبلة :

	void Event_Function_Name(ISender *pSender, IEventArgs *pEventArgs)
	{
		// الكود المراد تنفيده عند وقوع الحدث
	}

المناداة على الدالة التي توزع إشعارات و قوع الحدث تتم المناداة عليها بالشكل التالي :

Event_Name.Invoke(pSender_Object, pEvent_Args_Object);

الآن أصبح من الممكن اشتراك عناصر متعددة و دوالها في حدث واحد و حتى اشتراك عنصر واحد عدة مرات. و يمكن سحب الإشتراك بالمناداة على الدالة RemoveEventHandler.

في كل الأحوال, تبقى تساؤلات مطروحة عن احترام قواعد لغة سي++. و عما إذا كان من الممكن انشاء الموضوع برمته بشكل آخر أسهل و أبسط من هذه الطريقة. و طبعا السؤال

الذي ستجيب عليه الأيام عن و جود أخطاء...

في الملف المرفق ستجدون مشروع هذه الإضافة في حلته الجديدة مع برنامج صغير لتوضيح و دراسة الإضافات...

تحياتي...

event.rar

#3

هذا تصحيح للدالة EventManager::CheckInternalArray حيث لا يجب وضع الثقة في realloc لتعيد نفس قطعة الذاكرة السابقة مكبرة :

	void CheckInternalArray(void)
	{
		// التهيئة الأولى
		if(m_pIEArray == NULL) {
			m_pIEArray = (IEvent **)malloc(sizeof(IEvent *) * SIZE_CHUNK);
			if(m_pIEArray == NULL) throw "Run out of memory...";
			m_iSize += SIZE_CHUNK;
			for(int i = 0; i < m_iSize; i++) {
				m_pIEArray = NULL;
			}
			return;
		}
		// الحاجة لأماكن جديدة لتسجيل اشتراكات أخرى
		if(m_iCount >= m_iSize) {
			int oldSize = m_iSize;
			IEvent ** piea = (IEvent **)malloc(sizeof(IEvent *) * SIZE_CHUNK + m_iSize * sizeof(IEvent *));
			if(m_pIEArray == NULL) throw "Run out of memory...";
			m_iSize += SIZE_CHUNK;
			for(int i = 0; i < m_iSize; i++) {
				piea = (i < oldSize) ? m_pIEArray : NULL;
			}
			free(m_pIEArray);
			m_pIEArray = piea;
			return;
		}
		// TODO +> Free extra memory chunks if m_iSize - m_Count > SIZE_CHUNK
	}

تحياتي...

تم تعديل هذه المشاركة بواسطة محمد رضى في 3 أغسطس 2009 في 01:12

#4

اخواني الاعزاء سؤال واحد وبارك الله فيكم ماهو الفرق بين c++ والسي شارب بارك الله فيكم

−1
#5

موضوع رائع سلمت يمناك اخي الكريم

M.B.O

Mohamed Bin Othman

إبن المكلا

يابني البشر ماذا فعلتم بالبشر !!

إسـألـنـي

مواضيع مشابهة