Composite Widget
السلام عليكم ورحمة الله وبركاته ،
على الرغم من كمية الادوات Widgets الموجودة في كيوتي الا ان هذا لا يمنع المبرمجين من انشاء أدواتهم الخاصة ، فيمكنك بعدة طرق أن تنشئ أداتك الخاصة وإدراجها بسهولة الى صندوق الأدوات widgets box الموجود في ال Qt Designer.
درسنا اليوم سيكون عبارة عن مثال بسيط لانشاء أداة ليست جديدة ، وانما هي تركيب من أداتين -موجودتين مسبقا- وهو ما يعرف ب Composite Widgets .
هذه الطريقة -طريقة التركيب- تعتبر أبسط طريقة لانشاء أداة جديدة ، وطبعا امكانياتها محدودة ولن تستطيع التحكم بها كيفما تريد .
طريقة اخرى -وهي أفضل من السابقة – هي عن طريق الوراثة من widget معين ثم تقوم باعادة رسمها -او رسم جزء منها- واعادة تعريف الاحداث التي تريد تغييرها .
اما الطريقة الاخيرة فتكون عن طريق الوراثة المباشرة من QWidget ومن ثم اعادة تعريف معظم الدوال virtual function وتعريف طريقة رسم الاداة والاحداث أيضا.
في درسنا اليوم سنبدأ بطريقة بسيطة “خليط من الاولى والثانية” ، وان شاء الله في مرات قادمة نتقدم أكثر في الموضوع.
أداتنا ستكون clear line edit ، وهي عبارة عن QLineEdit يحوي على زر QToolButton بحيث يعمل على حذف النصوص text الموجودة داخل ال QLineEdit.
كأول تطبيق فعلي لهذه الاداة هي خانة البحث الموجودة في متصفح ال Firefox ، حيث يوجد زر للبحث وليس للحذف
، اي نفس الفكرة تقريبا.
صورة للأداة في مثال بسيط:

لاحظ الى الزر ، ايضا الجملة Enter your name وهي تختفي تلقائيا بمجرد الضغط على ال line edit .
حسنا ، سنبدأ أولا مع الدالة main في الملف main.cpp وسنحذف دالة save حتى يسهل التركيز على المهم .
#include "clearlineedit.h"
int main(int argc,char* argv[])
{
Q_INIT_RESOURCE(rc);
QApplication app(argc,argv);
QWidget* window = new QWidget;
QLabel* firstNameLabel = new QLabel("First Name");
QLabel* lastNameLabel = new QLabel("Last Name");
ClearLineEdit* firstNameEdit = new ClearLineEdit();
QLineEdit* lastNameEdit = new QLineEdit();
QPushButton* saveButton = new QPushButton("&Save");
QVBoxLayout* leftLayout = new QVBoxLayout;
leftLayout->addWidget(firstNameLabel);
leftLayout->addWidget(firstNameEdit);
QVBoxLayout* rightLayout = new QVBoxLayout;
leftLayout->addWidget(lastNameLabel);
leftLayout->addWidget(lastNameEdit);
QGridLayout* layout = new QGridLayout(window);
layout->addLayout(leftLayout,0,0,1,3);
layout->addLayout(rightLayout,1,0,1,3);
layout->addWidget(saveButton,2,2);
window->show();
return app.exec();
}
لا شيء يذكر هنا ، لاحظ فقط انشاء الكائن firstNameEdit وهو من النوع ClearLineEdit الذي سنقوم بتركيبه الان .
وكما يظهر في الصورة -اعلاه- أن الزر موجود بداخل ال line edit وهذا يدلنا على شيء بسيط وهو أن ال line edit سيكونparent للزر ، ونعلم -مسبقا- أنه عند عمل show لل parent فان هذا سيظهر جميع ال childs مباشرة .
السؤال الان ؟ كيف نظهر الزر بداخل ال line edit ؟
اذا قمنا باستخدام نظام ال Layout فان الزر سيكون بجوار ال line edit وليس بداخله !
الحل هنا هو بالوراثة من ال QLineEdit ومن ثم انشاء الزر بداخله وتحديد مكانه position يدويا عن طريق الدالة setGeometry.
كيف ؟ دعنا نشاهد ذلك ..
نبدأ مع ملف الرأس clearlineedit.h :
#define CLEARLINEEDIT_H
#include <QLineEdit>
class QToolButton;
class ClearLineEdit:public QLineEdit
{
.....
};
#endif // CLEARLINEEDIT_H
بداية توجد بعض موجهات المعالج المبدئي Preprocessor Directive والتي تحمي ملف الرأس من تضمينه أكثر من مرة واحدة.
وبعدها تم تعريف الفئة ClearLineEdit بأنها موروثة من الفئة QLineEdit .
ولأننا سنعرف slots جديدة يجب ان نقوم باضافة الماكرو Q_OBJECT في بداية تعريف الفئة .
الان اضافة الى دالة البناء والهدم المعروفتين ، توجد الدالة sizeHint() ، وظيفة هذه الدالة هي اعطاء ال ClearLineEdit حجم مثالي.
هذا الحجم width*height سيتم أخذه بالاعتبار عندما يدخل كائن الفئة في احد ال Layout ، حيث -كما نعرف- أن ال Layout يقوم بتغير ابعاد واحجام ال widgets بناءا على ال QSizePolicy و sizeHint .
يوجد لدينا دالتين slots ، الاولى للتأكد من أن السلسة التي تم ادخالها في ال line edit ليست خالية .
اما الدالة الاخرى فيتم تنفيذها عندما يتم الضغط على زر المسح .
void paintEvent(QPaintEvent*);
void resizeEvent(QResizeEvent*);
void focusInEvent(QFocusEvent*);
void focusOutEvent(QFocusEvent*);
QToolButton* m_clearButton;
bool m_isEmpty;
QString m_emptyText;
هذا هو القسم الاخير في ملف الرأس وفيه يتم اعادة تعريف بعض من الاحداث Override it التي سنحتاج اليها.
ايضا يوجد زر من النوع QToolButton ، متغير bool توضع قيمته true عندما لا يكون هناك اي نص مدخل في ال line edit ، أخيرا يوجد متغير يحمل الجملة Enter your name .
الان نذهب الى ملف ال implementation وهو clearlineedit.cpp :
دالة البناء تقوم بتهئية المتغيرات بقيم ابتدائية ، وتم وضع القيمة true للمتغير m_isEmpty دلالة على ان ال line edit خالي ، اما المتغير m_emptyText فيحمل جملة ستعرض في حالة لم يكن ال focus عليه -سنرى ذلك الان-.
resize(sizeHint());
// Call foucsIn/focusOut Event when the widget have keyboard focus.
setFocusPolicy(Qt::StrongFocus);
setFixedHeight(height());
بداية ، تم اعادة تحجيم ال line edit بحيث يكون حجمه مساوي للحجم المثالي ، بعد ذلك تم تعيين طريقة ال focus وهي Qt::StrongFocus والتي تدل على انه سيتم استدعاء الحدث focusInEvent عندما يكون مؤشر الكتابة على line edit سواءا كان بسبب الضغط بالماوس او عن طريق الضغط على مفتاح ال Tab في لوحة المفاتيح ، أيضا بنفس المبدأ سيتم استدعاء الحدث focusOutEvent بمجرد ان يغادر مؤشر الكتابة سواء كان السبب الضغط بالماوس على مكان اخر ام الضغط على tab.
بعد ذلك ، تم وضع ارتفاع ال line edit الحالي بانه ثابت ولن يتغير.
m_clearButton = new QToolButton(this);
m_clearButton->setIcon(QIcon(":/images/closetab.png"));
m_clearButton->setCursor(Qt::ArrowCursor);
m_clearButton->setAutoRaise(true);
m_clearButton->setToolTip(tr("clear"));
// set clearButton Position and Size inside the line edit
// size: 13*13
// pos: right
m_clearButton->setGeometry(width()-18,rect().top()+5,13,13);
هنا تم انشاء الزر وتعيين بعض الخصائص ، اولها ان child لل line edit ثم وضع ايقونة وتغيير موشر الكتابة “|” عند مرور الماوس على الزر الى ArrowCursor ، ومن ثم تفعيل ال AutoRaise ووضع tool tip بسيطة ، وبعدها تم تحديد مكان الزر داخل ال line edit ، وكذلك حجمة 13*13 .
هنا ، تم وضع stylesheet لل line edit بحيث تم عمل padding “ازاحة” ما بين الزر والنصوص ، وهذا حتى لا يتداخل النص مع الزر ، وفي الاخير تم عمل connection للزر بحيث يتم استدعاء الدالة clearClicked() في حالة الضغط عليه ، وكذلك تم ربط textChanged(const QString&) من ال line edit مع الدالة checkString(const QString&)) بحيث نستطيع معرفة هل ال line edit خالي ام لا .
يتم عن طريق هذه الدالة وضع الحجم المثالي وقد قمت باختيار القيم ،و يمكنك ان تستخدم الحجم المثالي للفئة QLineEdit وهو حل افضل -تقريبا-.
{
clear();
m_isEmpty = true;
}
هذه الدالة تعمل على مسح النصوص داخل ال line edit وتقوم بارجاع قيمة المتغير m_isEmpty الى حالته الافتراضية .
في هذه الدالة يتم فحص المدخلات ، وسياخذ المتغير m_isEmpty قيمة true في حالة كان لا يوجد نص ، او false غير ذلك .
{
// Update clearButton Position and Size.
m_clearButton->setGeometry(width()-18,rect().top()+5,13,13);
QLineEdit::resizeEvent(event);
}
الحدث resizeEvent ، ويتم استدعائه عندما نغيير حجم ال line edit ، وكل ما نريد عمله هنا هو ان نعيد تغيير مكان الزر الى المكان الصحيح .
{
// Pass painting the line edit to base class
QLineEdit::paintEvent(event);
// Paint the empty text message, if necessary
if (m_isEmpty) {
QPainter painter(this);
painter.setPen(QColor(Qt::lightGray));
QFont font;
font.setItalic(true);
QFontMetrics fm(font);
int textWidth = fm.width(m_emptyText);
int textHeight = fm.height();
QRect rect(5,5,textWidth,textHeight);
painter.drawText(rect,Qt::AlignLeft,m_emptyText);
}
}
هذا الحدث وظيفته اعادة رسم ال line edit ويتم استدعائه في العديد من الحالات منها عندما يتم عمل show لل linedit ، ايضا عند تغيير حجمه ، و عند استدعاء الدالة update/repaint فانه يقوم بالرسم مجددا ..
لاحظوا في بداية الدالة هذا الاستدعاء (QLineEdit::paintEvent(event والذي يقوم بمناداة دالة الرسم في الفئة QLineEdit حيث أننا هنا لا نريد رسم ال linedit نفسه ، وانما نريد رسم جملة Enter your name فقط .
الان في حالة كانت قيمة المتغير m_isEmpty صحيحة ، اي لا يوجد نص مدخل فان الجملة Enter your name سيتم رسمها باللون الرمادي .
{
QLineEdit::focusInEvent(event);
if (m_isEmpty) {
m_emptyText = "";
update();
}
}
يتم استدعاء هذا الحدث عندما يأتي مؤشر الكتابة الى ال line edit .
وكل ما سيفعله هذا الحدث هو في حالة كان لا يوجد نص على ال line edit فانه سيقوم بحذف الجملة ومن ثم اعادة رسمه مجددا بجمله خاليه.
{
QLineEdit::focusOutEvent(event);
if (m_isEmpty) {
m_emptyText = "Enter your name...";
update();
}
}
تعمل عكس الدالة السابقة ، ويتم استدعائها عندما يذهب ال focus ، وفي حالة كان ال line edit خالي فان الجملة Enter your name ترسم عليه .
ملاحظات:
* يمكنك ان تضيف زر البحث ، وبدلا من عمل clear قم بعمل emit لاي signal مثلا
وقم بربط هذا ال signal مع الكائن الذي تريد البحث فيه (QTextEdit مثلا).
* القيم في هذا المثال تجريبية، وفي ويندوز ظهر الزر صغير جدا ويجب زيادة حجمه.
صورة من ويندوز :
لاحظ ايضا ان حجم ال line edit الاعلى يختلف عن الاسفل ، وذلك بسبب اننا قمنا بتعريف sizeHint جديد للفئة ClearLineEdit.
الى هنا نصل الى نهاية الدرس ، اتمنى ان اكون قد وفقت في الشرح .
اي استفسار او خطأ في الكود او ملاحظة يرحب بها على المنتدى.
الى اللقاء.
635 مشاهدة أوسمة: custom widget
هذا الموضوع كتب بواسطة SudaNix في الإثنين، 28 سبتمبر 2009 على 11:46 ص في صنف عام.
