موضوع : Events in Qt Part C - Event Filtering
مثال عملی : یه ویدجت با سه لاین ادیت در داخل اون که فشردن کلید space در اونا باعث انتقال Focus به لاین ادیت بعدی میشه .
با سلام . فکر کنم کسی حوصله دانلود کردن نداره خوب همین جا میذارم راحت باشید.
یکی از ویژگی های بارز و مهم کیوتی اینه که شما میتونید یه شی از جنس [1] QObject رو مامور کنید پیام های یه QObject دیگه رو بگیره، حتی بدون اینکه اون کسی که پیام ها مالش بود متوجه بشه . بذارید توضیح بدم. فرض کنید شما می خواهید یه QLineEdit بسازید (منظورم اینه که ازش ارث ببرید و بعدش یه سری چیز بهش اضافه کنید) که وقتی روش کلیک شد یه کاری انجام بشه . همون طور که میدونید QLineEdit سیگنالی به نام clicked نداره بلکه برای انجام این کار باید mousePressEvent این QLineEdit جدیدمون رو باز نویسی کنیم.
ولی یه راه دیگه ای هم داریم. به پدر این لاین ادیت که مثلا یه QDialog هستش بگیم که اگه پیامی برای این لاین ادیت اومد خودت جواب بده ، این لاین ادیت بچه اس نمیتونه جواب بده ! به این عملیات میگیم Event Filtering . یعنی یه کسی بشه مسئول یکی دیگه . در این صورت تمام پیام های فرضا QLineEdit ما برای QDialog ما ارسال میشه . خوب یه مشکلی هست . به نظرتون چه مشکلی ؟
من ( منظور از من همون QDialog )که قصد نداشتم تمام پیام ها رو خودم بررسی کنم ، من فقط یه پیام رو میخواستم از QLineEdit پردازش کنم و بقیه رو بذارم بر عهده خودش . مثلا وقتی پیام[2] Paint برای من ارسال میشه که من نمیتونم لاین ادیت رو نقاشی کنم . خودش باید نقاشی کنه . پس چکار کنیم. خوب خیلی ساده است دیگه . من هرچی رو دوست دارم خودم(QDialog) پردازش میکنم و بقیه رو خودش (QLineEdit) پردازش کنه . خیلی خوب شد این طوری .
خوب بذارید یه مثال عملی انجام بدیم تا متوجه بشیم . آقا من میخوام یه سری QLineEdit روی QDialog ام داشته باشم که وقتی در یکی از QLineEdit ها کلید Space رو زدم Focus[3] به QLineEdit بعدی برسه . یه راه حل اینه که من کد زیر رو بنویسم
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) {
focusNextChild();
} else {
QLineEdit::keyPressEvent(event);
}
}
یعنی بیام و پیام صفحه کلید ( پیام فشرده شدن یه کلید ) که همون keyPressEvent هست رو در مورد QLineEdit باز نویسی کنم و توش بنویسم اگه کلید space زده شد برو سراغ شی بعدی رو دیالوگ ما که باید input focus رو دریافت کنه اگه غیر این بود به کلاس پایه QLineEdit میگم آقا کار خودتو بکن . کار خودش چی بود هیچی این کلید فشرده شده رو پردازش کنه و اگه قرار کاراکتری نمایش داده بشه نمایش بده. اگه این خط رو نذارید دیگه لاین ادیت چیزی نمایش نمیده.
دوتام مشکل داره این کار . یکیش اینه که ما حوصله نداریم واسه این یه کارساده یه کلاس جدید تعریف کنیم. دوم اینکه حالا اگه قرار شد QComboBox هم توی این فرم قرار بگیره دیگه هیچی ! بازم باید یه کلاس جدید برای اون تعریف کنیم و همین کارا رو برای keyPressEvent اش انجام بدیم .
ولی این کار به راحتی با استفاده از event filtering قابل انجامه. خوب انجام این کار دو مرحل داره:
1. باید به شی ای که میخوایم پیام ها شو بدزدیم بگیم که ما قصد انجام این کارو داریم . این کار با استفاده از تابع installEventFilter قابل انجامه. پس پیام های A از این به بعد برای B ارسال میشه.
2. B باید پیام ها رو در تابع eventFilter پردازش کنه.
در ادامه یه مثال عملی از این موضوع خواهیم دید.
کلاسی از جنس QWidget به صورت زیر تعریف میکنیم :
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QLineEdit;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QLineEdit *_led_a, *_led_b, *_led_c;
protected:
bool eventFilter(QObject *, QEvent *);
};
#endif // WIDGET_H
همون طور که میبینید قصد داریم سه تا QLineEdit داشته باشیم رو فرممون.سازنده ما به صورت زیر خواهد بود:
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QVBoxLayout *hlay = new QVBoxLayout( this );
_led_a = new QLineEdit;
_led_a->installEventFilter(this);
hlay->addWidget(_led_a);
_led_b = new QLineEdit;
_led_b->installEventFilter(this);
hlay->addWidget(_led_b);
_led_c = new QLineEdit;
_led_c->installEventFilter(this);
hlay->addWidget(_led_c);
}
همونطور که میبیند برای هر سه LineEdit مون با استفاده از تابع installEventFilter شی کلاس ای از جنس QWidget که همون widget (نام کلاس ارث برده شده ) رو مسئول پیام های این سه لاین ادیت قرار دادیم.
bool Widget::eventFilter(QObject *target, QEvent *event)
{
if (target == _led_a || target == _led_b
|| target == _led_c ) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space) {
focusNextChild();
return true;
}
}
}
return QWidget::eventFilter(target, event);
}
برای تابع eventFilter رو مقدار ارسال خواهد شد. یکی اینکه پیام در اصل ماله کیه . دوم اصلا پیامه چیه ؟ ما اول میام و چک میکنیم که آیا پیام ارسالی مال یکی از QLineEdit هاست یا نه . و بعدش میام میبینم که آیا پیام ارسالی اصلا از جنس پیام صفحه کلیدی است یا نه . چون قرار شد همه پیام ها برای این تابع ارسال بشه. خوب پس پیام از جنس صفحه کلیدی یعنی از جنس شی ای از کلاس QKeyEvent ( کلاسی است که اطلاعات تکملی در مورد پیام صفحه کلید را دارد مثلا اینکه کدام کلید فشرده شده ) ولی پارامتر ارسالی از جنس QEvent هستش که پایه تمام کلاس های Event مث QMoseEvent و QKeyEvent هستش . همون قاعده چند ریختی دیگه . خوب با یه Cast ساده یه شی از جنس QKeyEvent به دست میاریم و بعدش میبینیم که آیا کلید فشرده شده space هست یا نه اگه هست که ما پیام را پردازش میکنیم و مقدار true برمیگردانیم که به کیوتی بگیم آقا ما این پیام رو پردازش کردیم .اگه ما مقدار false بر میگردوندیم کیوتی تصور میکرد که پیام پردازش نشده و به شی QLineEdit مربوطه میفرستادش پیام رو.
خوب اینم از این مثال و از این آموزش
پاورقی
[1]
منظور از یه شی از جنس QObject شی است که یا خودش از QObject ساخته شده باشد یا به صورت مستقیم یا غیر مستقیم از QObject به ارث برده شده باشد
[2]
دقیقا پیامی با نام WM_PAINT برای Window procedure ما ارسال میشه.
[3]
ببینید نکته ای که هست اینه : برنامه ها از یه سری Widget درست شدنددیگه مثلا دکمه و لاین ادیت و اینا . پیام های صفحه کلید در آن واحد فقط واسه یکی میشه فرستاده بشه . مثلا شما میتونید در یه لحظه واسه دوتا Note Pad تایپ کنید نمیشه که . حالا این ورودی های صفحه کلید ( پیام های صفحه کلید ) واسه کی فرستاده میشه ؟ واسه کسی که Input Focus داشته باشه . چه کسی Input Focus رو داره . مثلا در مورد LineEdit زمانی که Caret در داخلش چشمک میزنه یعنی Input Focus داره.
پیشنهاد : محض اطلاع خواستم بگم که IP Editor ویندوز رو تا حالا دیدین !