في هذا الجزء من دورة البرمجه في ال GTK+ سوف نتكلم عن نظام الاحداث في مكتبة ال GTK+ .
مكتبة ال GTk+ هي مكتبة حدثيه تنتظر وقوع الاحداث وهو مصطلح برمجي باسم (event driven) . كل تطبيقات الواجه الرسوميه هي تطبيقات حدثيه . التطبيق يقوم ببدأ دواره اساسيه main loop , التي باستمرار تقوم بتفقد الاحداث الجديده . اذا لم يكن هناك اي حدث , فان التطبيق ينتظر ولا يفعل ايش شيئ. في مكتبة ال GTK+ الحدث (event) هو رساله من الخادم x (x server) . عندما يصل الحدث الي widget معين كالنافذه مثلا , يمكن ان يتصرف الwidget ببعث اشاره. مبرمج ال GTK+ يستطيع ان يربط اشاره محدده بداله عكسيه . وهي داله تقوم بالتعامل والرد علي هذه الاشاره . وبدونها لا نستطيع التعامل مع الاشاره .
#include <gtk/gtk.h> void button_clicked(GtkWidget *widget, gpointer data) { g_print("clicked\n"); } int main( int argc, char *argv[]) { GtkWidget *window; GtkWidget *fixed; GtkWidget *button; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "GtkButton"); gtk_window_set_default_size(GTK_WINDOW(window), 230, 150); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); fixed = gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(window), fixed); button = gtk_button_new_with_label("Click"); gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50); gtk_widget_set_size_request(button, 80, 35); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show_all(window); gtk_main(); return 0; }
في تطبيقنا لدينا اشارتين . اشرة النقر (clicked) واشارة التدمير (destroy) .
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), NULL);
نقوم باستخدام الداله g_signal_connect() لكي نقوم بالربط بين الاشاره والداله العكسيه button_clicked() .
void button_clicked(GtkWidget *widget, gpointer data) { g_print("clicked\n"); }
هذه الداله العكسيه سوف تقوم بطبع كلمة "clicked" علي شاشة الكونسول . ال parameter الاول في الداله العكسيه هو الكائن الذي يبعث الاشاره . في حالتنا هذا الكائن هو زرار click . الparameter الثاني اختياري . يمكننا ان نرسل بعض البيانات الي الداله العكسيه . في حالتنا لم نقوم بارسال اي بيانات . قمنا باعطائه قيمة NULL في الداله g_signal_connect() .
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
اذا قمنا بضغط العلامه x الموجوده في الجزء اليميني الاعلي من النافذه او قمنا بضغط المفتاحين Alt+F4 , يتم ارسال اشارة تدمير (destroy) . ثم نستخدم الداله gtk_main_quit() كداله عكسيه ونستدعيها , والتي تنهي التطبيق بشكل كامل وصحيح .
في المثال القادم نعرض كيف يمكن ان تقوم بالرد علي اشارة تحريك النافذه.
#include <gtk/gtk.h> void frame_callback(GtkWindow *window, GdkEvent *event, gpointer data) { int x, y; char buf[10]; x = event->configure.x; y = event->configure.y; sprintf(buf, "%d, %d", x, y); gtk_window_set_title(window, buf); } int main(int argc, char *argv[]) { GtkWidget *window; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 230, 150); gtk_window_set_title(GTK_WINDOW(window), "Simple"); gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE); g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), G_OBJECT(window)); g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(frame_callback), NULL); gtk_widget_show(window); gtk_main(); return 0; }
في المثال , نقوم بعرض الموقع الحالي للجزء الاعلي اليساري من النافذه في شريط العنوان title bar .
gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);قناع الحدث الخاص بال widget يحدد , اي نوع من الاحداث يستقبلها widget معين . بعد الاحداث قد تم تهيئتها بالفعل من قبل , بعض الاحداث الاهري يجب علينا ان نضعها في قناع الاحداث (event mask) . الداله gtk_widget_add_event() تضع الحدث GTK_CONFIGURE الي القناع . هذا النوع من الاحداث التي قمنا باضافته يحسب كل المساحات , المواقع , والمكان في الكومه stack order من الاحداث .
g_signal_connect(G_OBJECT(window), "configure-event", G_CALLBACK(frame_callback), NULL);
هذا الحدث يبعث في حالة اذا صدر حدث تغير حجم او مكان او ترتيب في الكومه .
void frame_callback(GtkWindow *window, GdkEvent *event, gpointer data) { int x, y; char buf[10]; x = event->configure.x; y = event->configure.y; sprintf(buf, "%d, %d", x, y); gtk_window_set_title(window, buf); }
الداله العكسيه لديها ثلاث parameters . الكائن الذي بعث الاشاره , ونوع بيانات GdkEven وبيانات اختياريه . ونقوم بحساب مكان النافذه بالمتغيرات x , y ونقوم بوضعها في شريط العناون .
Figure: Move event
في المثال القادم , سوف نعرض كيف يمكننا ان نريد علي اشارة دخول (enter signal) . هذه الاشاره تبعث عندما ندخل في مساحة widget معينه بمؤشر الفأره .
#include <gtk/gtk.h> void enter_button(GtkWidget *widget, gpointer data) { GdkColor color; color.red = 27000; color.green = 30325; color.blue = 34181; gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color); } int main( int argc, char *argv[]) { GtkWidget *window; GtkWidget *fixed; GtkWidget *button; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 230, 150); gtk_window_set_title(GTK_WINDOW(window), "enter signal"); fixed = gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(window), fixed); button = gtk_button_new_with_label("Button"); gtk_widget_set_size_request(button, 80, 35); gtk_fixed_put(GTK_FIXED(fixed), button, 50, 50); g_signal_connect(G_OBJECT(button), "enter", G_CALLBACK(enter_button), NULL); g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show_all(window); gtk_main(); return 0; }
هنا نقوم بتغير لون خلفية زرار بمجرد ان يقوم مؤشر الفأره بالمرور عليها .
g_signal_connect(G_OBJECT(button), "enter", G_CALLBACK(enter_button), NULL);
نقوم باستدعاء الداله العكسيه عندما يتم بعث اشارة دخول.
GdkColor color; color.red = 27000; color.green = 30325; color.blue = 34181; gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &color);
داخل الداله العكسيه قمنا بتغير لون الخلفيه بواسطة استدعاء الداله gtk_widget_modify_bg() بداخل الداله العكسيه .
يمكننا ان نفصل اتصال دالة عكسيه من اشاره معينه . هذا الكود يشرح هذه العمليه .
#include <gtk/gtk.h> int handler_id; void button_clicked(GtkWidget *widget, gpointer data) { g_print("clicked\n"); } void toogle_signal(GtkWidget *widget, gpointer window) { if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { handler_id = g_signal_connect(G_OBJECT(window), "clicked", G_CALLBACK(button_clicked), NULL); } else { g_signal_handler_disconnect(window, handler_id); } } int main( int argc, char *argv[]) { GtkWidget *window; GtkWidget *fixed; GtkWidget *button; GtkWidget *check; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 250, 150); gtk_window_set_title(GTK_WINDOW(window), "Disconnect"); fixed = gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(window), fixed); button = gtk_button_new_with_label("Click"); gtk_widget_set_size_request(button, 80, 30); gtk_fixed_put(GTK_FIXED(fixed), button, 30, 50); check = gtk_check_button_new_with_label("Connect"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE); gtk_fixed_put(GTK_FIXED(fixed), check, 130, 50); handler_id = g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), NULL); g_signal_connect(G_OBJECT(check), "clicked", G_CALLBACK(toogle_signal), (gpointer) button); g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_widget_show_all(window); gtk_main(); return 0; }
في مثالنا ,لدنيا صندوق تحقق (check box) , علي حسب علامة الصندوق يفصل او يربط صنددوق التحقق الداله العكسيه من الربط او الاتصال بالاشاره.
handler_id = g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(button_clicked), NULL);
الداله g_signal_connect() ترجع قيمه عدديه , والتي تميز الداله العكسيه التي تم ربطها.
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { handler_id = g_signal_connect(G_OBJECT(window), "clicked", G_CALLBACK(button_clicked), NULL); } else { g_signal_handler_disconnect(window, handler_id); }
هذا الكود يقوم بتحديد حالة صندوق التحقق . تقوم بربط الداله العكسيه بالاشاره اذا كان الصندوق معلم . غير ذلك تقوم بفصل الداله العكسيه من الاشاره.
Figure: Disconnect
في مثالنا القادم , نعرض خاصيه مثيره للاهتمام. سوف نقوم بعرض كل النوافذ التي لا تحتوي علي عدود ونتعلم كيف يمكننا ان نقوم بسحب ووضع مثل هذه النافذه.
#include <gtk/gtk.h> gboolean on_button_press (GtkWidget* widget, GdkEventButton * event, GdkWindowEdge edge) { if (event->type == GDK_BUTTON_PRESS) { if (event->button == 1) { gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)), event->button, event->x_root, event->y_root, event->time); } } return FALSE; } int main( int argc, char *argv[]) { GtkWidget *window; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 230, 150); gtk_window_set_title(GTK_WINDOW(window), "Drag & drop"); gtk_window_set_decorated(GTK_WINDOW (window), FALSE); gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK); g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(on_button_press), NULL); g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), G_OBJECT(window)); gtk_widget_show(window); gtk_main(); return 0; }
هذا المثال يشرح سحب ووضع نافذه بغير حدود .
gtk_window_set_decorated(GTK_WINDOW (window), FALSE);نقوم بنزع التنسيقات من النافذه . هذا يعني ان هذه النافذه لن تحتوي علي شريط عنوان او حدود .
g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(on_button_press), NULL);
نقوم بربط النافذه بالحدث button-press-event بالداله العكسيه .
gboolean on_button_press (GtkWidget* widget, GdkEventButton * event, GdkWindowEdge edge) { if (event->type == GDK_BUTTON_PRESS) { if (event->button == 1) { gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)), event->button, event->x_root, event->y_root, event->time); } } return FALSE; }
بداخل الداله العكسيه on_button_press نقوم بتأدية وظيفة السحب والوضع . نقوم بتفقد اذا ماكان زرار الفأرة الشمال قم تم نقره .ثم نستدعي الداله gtk_window_being_move_drag() .
في المثال القادم نقوم بتوضيح مثال علي مؤقت . نقوم باستخدام المؤقتات عندما يكون لدينا وظيفة نقوم بعملها بشكل متكرر . يمكنها ان تكون ساعه , عداد تنازلي , او حتي مؤثرات بصريه .
#include <cairo.h> #include <gtk/gtk.h> #include <time.h> static char buffer[256]; static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) { cairo_t *cr; cr = gdk_cairo_create(widget->window); cairo_move_to(cr, 30, 30); cairo_show_text(cr, buffer); cairo_destroy(cr); return FALSE; } static gboolean time_handler(GtkWidget *widget) { if (widget->window == NULL) return FALSE; time_t curtime; struct tm *loctime; curtime = time(NULL); loctime = localtime(&curtime); strftime(buffer, 256, "%T", loctime); gtk_widget_queue_draw(widget); return TRUE; } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(darea, "expose-event", G_CALLBACK(on_expose_event), NULL); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 170, 100); gtk_window_set_title(GTK_WINDOW(window), "timer"); g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window); gtk_widget_show_all(window); time_handler(window); gtk_main(); return 0; }
نقوم بعرض الوقت الحالي , ونستخدم المكتبة االثنائية البعد cairo 2d .
g_signal_connect(darea, "expose-event", G_CALLBACK(on_expose_event), NULL);
سوف نقوم برسم الوقت بداخل الداله العكسيه on_expose_event() . هذه الداله العكسيه مربوطه بالاشارة expose-event . هذه الاشاره تبعث في حالة ان النافذه سوف يتم اعادة رسمها .
g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);هذه الداله تقوم بتسجيل المؤقت . الداله time_handler() تستدعي بشكل متكرر بنمط ثابت ومعتاد . في حالتنا في كل ثانيه . يتم استدعاء الدالة الي ان ترجع قيمة FALSE .
time_handler(window);هذه تقوم باستدعاء دالة المؤقت فورا , والا سوف يكون لدينا تأخير بثانيه .
cairo_t *cr; cr = gdk_cairo_create(widget->window); cairo_move_to(cr, 30, 30); cairo_show_text(cr, buffer); cairo_destroy(cr);
هذا الكود يقوم برسم الوقت علي النافذه . لمزيد من المعلومات حول المكتبة الثنائيه للرسم Cairo 2D , انظر دورة Zetcode الخاصه بها هنا.
if (widget->window == NULL) return FALSE;عندما يتم تدمير النافذه , قد يتم في نفس الوقت استدعاء دالة الموقت . هذا السطر سوف يمنع ان تقوم دالة الموقت بالعمل علي widget قد تم تدميره بالفعل .
time_t curtime; struct tm *loctime; curtime = time(NULL); loctime = localtime(&curtime); strftime(buffer, 256, "%T", loctime);
هذا السطر يقوم بتحديد الوقت المحلي الحالي.
gtk_widget_queue_draw(widget);هذا سوف يقوم بابطال عمل النافذه , الامر الذي سوف يبعث باشارة expose-event .
Home Contents Top of Page