|
|
|
الدرس الثالث:تعريف slot جديدة |
|
بعد ان تعرفنا على كيفية الوراثة من QWidget و كيفية تقسم البرنامج الى عدة ملفات ،،
نعود الان مجددا الى المشكلة العنيدة ، كيف نعرف slot ؟
لتعريف slots او signals جديدة ، يجب وضع العبارة Q_OBJECT في اول الاعلان عن الفئة ، وبالتحديد في الملف الذي يحمل الامتداد .h ، دلالة على ان هذه الفئة تريد تعريف اشارات ودوال slots جديدة.
هذه العبارة Q_OBJECT هي عبارة عن ماكرو Macro ، يبحث عنها مترجم داخلي في Qt يسمى moc وهي اختصار لـ
Meta Object Complier .
هذا المترجم وظيفته البحث عن ملفات الرأس التي تحمل العبارة Q_OBJECT ، ثم يقوم يتحويل ملفات الرأس المطابقة الى ملفات تبدأ بـ moc_ وتحمل الامتداد .cpp ، حتى يستطيع مترجم سي++ القياسي التعامل معها.
من شروط هذا المترجم moc هو وجود ملف رأس منفصل للاعلان عن الفئة التي تريد تعريف signal/slot جديدة ، كذلك وجود العبارة Q_OBJECT.
هل عرفت اثبات نظرية "عزل الواجهة عن التطبيق" !! .
سندرس الان فكرة تعريف signals/slots جديدة ، على مثال بسيط ، ثم بعدها سنطبق على المثال الذي اختتمنا به الجزء الاول.
المثال الاول :
سنكتب برنامج لتحويل الحروف من كبيرة upper case الى صغير lower case .
شكل المخرج يجب ان يكون كالاتي:

تحليل سريع :
كما يظهر على الصورة ، يوجد كائنين من QLineEdit و كائنين من QLabel ، وكذلك يوجد مخطط افقي
ومخطط عمودي .
كائني QLineEdit متزامنين ، بحث ان تم كتابة حرف في اي منهما ، فسوف يظهر على الكائن الاخر،ولكن بعكس حالة الحرف .
نريد ان نربط اي حدث في QLineEdit يرسل في حالة التعديل عليه ، ارجع الى التوثيق ، ستجد ان هناك حديثين متشابهان تقريبا ، الاول textChanged والاخر textEdited ، سنتخار الاخير ، لانه اثناء التعديل على كائن QLineEdit نريد ان يظهر الحرف في الكائن الاخر.
اذا باعتبار نموذج الربط هذا :
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
|
فان المرسل هو احد كائنات QLineEdit ، ولنسمه upperCaseLineEdit ، والمستقبل هو الكائن الاخر ولنسمه lowerCaseLineEdit.
الكائن الاول سيرسل حدث textChanged والذي يأخذ السلسة التي تم كتابتها فيه كوسيط.
ترسل السلسلة المكونة من احرف كبيرة ، الى الكائن الثاني والذي يجب ان ينفذ دالة ما بحيث تودي الغرض .
نرجع الى التوثيق ، نبحث عن جميع ال slots ،،، لن نجد ما نريد !
فقط اقرب شيء هو دالة تعيين نص setText . وهذه سوف تقوم بكتابة النص نفسه !
اذا نحتاج الى slot جديد بحيث يحول من الاحرف الكبيرة الى احرف صغيرة ،،
وكذلك slot اخر لكي يحول من حرف صغير الى كبير .
الشفرة البرمجية :
الملف conventor.h:
1- #ifndef CONVENTOR_H 2- #define CONVENTOR_H 3- 4- #include <QWidget> 5- 6- class QLineEdit; 7- 9- { 10- Q_OBJECT 11- 12- public: 14- 15- public slots: 18- 19- private: 22- }; 23- 24- #endif // CONVENTOR_H
|
ملف الرأس conventor.h تم فصله عن التطبيق وذلك لاننا نريد تعريف slots ، كذلك تم الوراثة من الفئة
QWidget ، لكي نضمن جميع خصائص اي widget.
السطر 6: يحوي اعلان عن فئة QLineEdit ، وذلك لاننا نريد تعريف مؤشر من هذه الفئة .
ومن الممكن ان نستبدل هذا الاعلان بعملية تضمين ملف الرأس QLineEdit ، ولكن لان عملية التضمين
ستأخذ وقتا من المترجم اثناء قراءة الشفرة ، لذا من الافضل دائما وضع اعلان عن الفئة اذا كنا نريد تعريف مؤشر منها .
السطر 10 : هذا السطر يحوي الاعلان عن الماكرو ، وهذه اشارة للمترجم moc بان هذا الملف بحاجة
الى ترجمة بواسطته ، النتيجة من عملية الترجمة هو الملف moc_conventor.cpp .
السطر 15: يحوي على محدد لم تستعمله من قبل وهو slots .
وجميع الدوال المعرفة تحته تعتبر دوال slot ،اي يمكن ان نربطها مع حدث ما في عبارة connect.
مستوى حماية المحدد public or private or protected ، لا تاثير لها اذا كنت تستخدم هذه الدوال
للربط فقط ، اي في عبارة connect.
اما اذا كنت تريد استخدامها كدوال تعين setter ، فيجب الانتباه الى مستوى الحماية.
الاسطر 16 و 17 :
لدينا دالتين ، الاولى تقوم بكتابة السلسة المررة اليها كأحرف كبيرة في الكائن upperCaseLineEdit.
اما الدالة الاخر ، تقوم بكتابة السلسة المررة اليها كأحرف صغيرة في الكائن lowerCaseLineEdit.
الاسطر 19 الى 21 :
تم وضع المحدد الخاص private وذلك لتعريف الكائنين upperCaseLineEdit و lowerCaseLineEdit.
ومن الممكن ان نقوم بتعريف الكائنات في دالة البناء ، ولكن في هذا المثال يجب ان تكون هذه الكائنات موجودة في الفئة نفسها ، وذلك حتى تستطيع جميع الدوال بما فيها ال slots الوصول اليهم.
الملف conventor.cpp :
#include <QLineEdit> #include <QLabel> #include <QGridLayout> #include "conventor.h" Conventor ::Conventor(QWidget* parent ):{ // Create Widgets upperCaseLineEdit=new QLineEdit; lowerCaseLineEdit=new QLineEdit; // Establish Connection connect(upperCaseLineEdit ,SIGNAL(textEdited (const QString&)),this,SLOT (setLower (const QString&))); connect(lowerCaseLineEdit ,SIGNAL(textEdited (const QString&)),this,SLOT (setUpper (const QString&))); // Create Layouts layout->addWidget(upperCaseLabel,0,0); layout->addWidget(upperCaseLineEdit,0,1); layout->addWidget(lowerCaseLabel,1,0); layout->addWidget(lowerCaseLineEdit,1,1); setLayout(layout); setWindowTitle("Upper/Lower case Conventor v1.0"); } void Conventor ::setUpper(const QString& str ) { if( upperCaseLineEdit->text() == tmp ) return; upperCaseLineEdit->setText(tmp); } void Conventor ::setLower(const QString& str ) { if( lowerCaseLineEdit->text() == tmp ) return; lowerCaseLineEdit->setText(tmp); }
|
على نفس المنهج المتبع دائما ، حيث نبدأ بانشاء ال widgets ، ثم وضع الخصائص ، ثم الربط
ثم انشاء المخططات وتعبئتها بال widgets ، واخيرا وضع المخطط على كائن الفئة.
الامر الذي قد يبدو جديدا هذه المرة ، هو في الربط او تأسيس الاتصال ..
حيث ان الكائن المستقبل اصبح هو this ، ونعلم ان this هي مؤشر لكائن الفئة .
اذا بمعنى اخر ، فان عبارة الربط تقول ، انشيء اتصال بحيث ان المرسل هو upperCaseLineEdit والحدث المرسل هو textEdited ، اما المستقبل هو كائن هذه الفئة ، والدالة التي يجب تنفيذها هي setLower .
لاحظ انه يجب ان تتطابق انواع الوسائط في عملية الربط .
كذلك انشئ اتصال اخر بين lowerCaseLineEdit ، وكائن هذه الفئة .
وبالنسبة للمخطط ، فلم نستخدم لا الافقي والا العمودي ، حيث استخدمنا النوع الشبكي ، وسندرسه لاحقا .
نأتي لدالة setUpper ، حيث تستقبل هذه الدالة سلسلة نصية .
هدف هذه الدالة هو وضع قيمة للكائن upperCaseLineEdit ، لكن بعكس حالة الاحرف.
تستخدم الدالة toUpper والتي ستقوم بعملية التحويل .
عمل هذه الدالة واضح جدا ، لكن الشئ الغريب هو وجود شرط يتأكد من ان القيمة المرسلة الى الدالة
هل هي نفس قيمة الكائن upperCaseLineEdit !!
اذا كان نعم ، فلا يوجد داعي للاكمال ، لان السلسة هي نفسها ولا يوجد اختلاف !
هذا الشرط يلعب دور كبير في عملية المزامنة بين كائنين او اكثر ، حيث بدونه سوف ندخل في حلقة
لا نهائية .
اما الدالة setLower ، فهي تعمل بنفس فكرة الدالة السابقة ولكن تحول الاحرف من كبير الى صغير.
اهم شيء يجب ان تتذكره دائما عند كتابة تعريف اي دالة slots هو شرط التوقف !
حيث بدون هذا الشرط سنقع في مشكلة كبيرة !
انظر الى هذين السطرين لتعرف المشكلة !
connect(upperCaseLineEdit ,SIGNAL(textEdited (const QString&)),this,SLOT (setLower (const Qstring &))); connect(lowerCaseLineEdit ,SIGNAL(textEdited (const QString&)),this,SLOT (setUpper (const Qstring &)));
|
ولنأخذ هذا السيناريو ،،
نفرض انك قمت بالكتابة على upperCaseLineEdit ، في هذه الحالة سوف "تبعث" اشارة تدل على وجود تعديل ،، هذا الاشارة قمنا بربطها مع الدالة setLower .
وفي حالة عدم وجود الشرط في الدالة setLower فانها ستقوم بتحويل السلسة الى lower case ثم تضع النتيجة في الكائن lowerCaseLineEdit .
فورا سيتم "بعث" اشاره تدل على وجود تعديل في الكائن lowerCaseLineEdit ، وهذه الاشارة قمنا
بربطها مع الدالة setUpper.
وفي حالة عدم وجود الشرط في الدالة setUpper فانها ستقوم بتحويل السلسلة الى upper case ثم تضع النتيجة في الكائن upperCaseLineEdit .
من جديد سيتم ارسال اشاره ......الخ.
لذلك لا بد من وجود شرط التأكد في اي دالة slots ، خاصة في حالة التزامن بين الكائنات.
الملف main.cpp :
#include <QApplication> #include "conventor.h" int main(int argc,char* argv[]) { Conventor c; c.show(); return app.exec(); }
|
من الان وصاعدا ، لن اذكر اسم الملف main.cpp مرة اخرى .
وذلك لان محتوياته اصبحت معروفة جدا ، ولن تتغير الا في حالات نادرة.
مجرد تضمين لملف الرأس للفئة التي تم كتابتها ، ثم انشاء كائن منها ، واستدعاء الدالة show.
ملاحظة على الشفرة السابقة :
يجب ادخال حرف كبير في الكائن الاول ، وحرف صغير في الثاني لكي يعمل بشكل جيد.
وكان من الممكن ان اتحكم في نوع الاشياء التي يجب كتابتها عن طريق QValidator.
ولكن حتى لا يتغير الموضوع الى موضوع اخر ، ففضلت عدم استخدامه ، وسوف نعود لهذا المثال
لاحقا ، عندما ندرس التحكم في المدخلات.
نذهب الى المثال الاخر ..
ا.هـ
| إسم الكاتب |
تاريخ الإضافة |
التقييم / المقيمين |
زيارات الدرس |
| SudaNix |
19/09/2008 |
27 / 3 |
828 |
|
|
|
|