原文: http://zetcode.com/gui/gtk2/gtkevents/

在 GTK+ 编程教程的这一部分中,我们讨论事件系统。

GTK+ 是事件驱动的系统。 所有 GUI 应用都是事件驱动的。 应用启动一个主循环,该循环不断检查新生成的事件。 如果没有事件,则应用将等待并且不执行任何操作。 在 GTK+ 中,事件是来自 X 服务器的消息。 当事件到达窗口小部件时,它可以通过发出信号对此事件做出反应。 GTK+ 程序员可以将特定的回调连接到信号。 回调是对信号做出反应的处理函数。

点击按钮

触发按钮时,它会发送clicked信号。 可以通过鼠标指针或使用空格键触发按钮(只要按钮具有焦点)。

buttonclick.c

  1. #include <gtk/gtk.h>
  2. void button_clicked(GtkWidget *widget, gpointer data) {
  3. g_print("clicked\n");
  4. }
  5. int main(int argc, char *argv[]) {
  6. GtkWidget *window;
  7. GtkWidget *halign;
  8. GtkWidget *btn;
  9. gtk_init(&argc, &argv);
  10. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  11. gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  12. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  13. gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  14. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  15. halign = gtk_alignment_new(0, 0, 0, 0);
  16. btn = gtk_button_new_with_label("Click");
  17. gtk_widget_set_size_request(btn, 70, 30);
  18. gtk_container_add(GTK_CONTAINER(halign), btn);
  19. gtk_container_add(GTK_CONTAINER(window), halign);
  20. g_signal_connect(G_OBJECT(btn), "clicked",
  21. G_CALLBACK(button_clicked), NULL);
  22. g_signal_connect(G_OBJECT(window), "destroy",
  23. G_CALLBACK(gtk_main_quit), NULL);
  24. gtk_widget_show_all(window);
  25. gtk_main();
  26. return 0;
  27. }

在应用中,我们有两个信号:clicked信号和destroy信号。

  1. g_signal_connect(G_OBJECT(btn), "clicked",
  2. G_CALLBACK(button_clicked), NULL);

我们使用g_signal_connect()函数将clicked信号连接到button_clicked()回调。

  1. void button_clicked(GtkWidget *widget, gpointer data) {
  2. g_print("clicked\n");
  3. }

回调将"clicked"字符串打印到控制台。 回调函数的第一个参数是发出信号的对象。 在我们的例子中是单击按钮。 第二个参数是可选的。 我们可能会向回调发送一些数据。 在我们的案例中,我们没有发送任何数据; 我们为g_signal_connect()函数的第四个参数提供了NULL值。

  1. g_signal_connect(G_OBJECT(window), "destroy",
  2. G_CALLBACK(gtk_main_quit), NULL);

如果按标题栏右上角的 x 按钮,或按 Atl + F4 ,则会发出destroy信号。 调用gtk_main_quit()函数,该函数将终止应用。

移动窗口

下一个示例显示了我们如何应对窗口移动事件。

moveevent.c

  1. #include <gtk/gtk.h>
  2. void configure_callback(GtkWindow *window,
  3. GdkEvent *event, gpointer data) {
  4. int x, y;
  5. GString *buf;
  6. x = event->configure.x;
  7. y = event->configure.y;
  8. buf = g_string_new(NULL);
  9. g_string_printf(buf, "%d, %d", x, y);
  10. gtk_window_set_title(window, buf->str);
  11. g_string_free(buf, TRUE);
  12. }
  13. int main(int argc, char *argv[]) {
  14. GtkWidget *window;
  15. gtk_init(&argc, &argv);
  16. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  17. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  18. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  19. gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);
  20. g_signal_connect(G_OBJECT(window), "destroy",
  21. G_CALLBACK(gtk_main_quit), G_OBJECT(window));
  22. g_signal_connect(G_OBJECT(window), "configure-event",
  23. G_CALLBACK(configure_callback), NULL);
  24. gtk_widget_show(window);
  25. gtk_main();
  26. return 0;
  27. }

在示例中,我们在标题栏中显示了窗口左上角的当前位置。

  1. gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

小部件的事件掩码确定特定小部件将接收的事件类型。 一些事件是预先配置的,其他事件必须添加到事件掩码中。 gtk_widget_add_events()GDK_CONFIGURE事件类型添加到掩码。 GDK_CONFIGURE事件类型说明了窗口的所有大小,位置和堆叠顺序。

  1. g_signal_connect(G_OBJECT(window), "configure-event",
  2. G_CALLBACK(configure_callback), NULL);

当窗口小部件的窗口的大小,位置或栈发生更改时,将发出configure-event

  1. void configure_callback(GtkWindow *window,
  2. GdkEvent *event, gpointer data) {
  3. int x, y;
  4. GString *buf;
  5. x = event->configure.x;
  6. y = event->configure.y;
  7. buf = g_string_new(NULL);
  8. g_string_printf(buf, "%d, %d", x, y);
  9. gtk_window_set_title(window, buf->str);
  10. g_string_free(buf, TRUE);
  11. }

回调函数具有三个参数:发出信号的对象,GdkEvent和可选数据。 我们确定 x,y 坐标,构建一个字符串,并将其设置为窗口标题。

GTK  事件和信号 - 图1

图:移动事件

输入信号

以下示例显示了我们如何对enter信号做出反应。 当我们使用鼠标指针进入小部件的区域时,将发出enter信号。

entersignal.c

  1. #include <gtk/gtk.h>
  2. void enter_button(GtkWidget *widget, gpointer data) {
  3. GdkColor col = {0, 27000, 30000, 35000};
  4. gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &col);
  5. }
  6. int main(int argc, char *argv[]) {
  7. GtkWidget *window;
  8. GtkWidget *halign;
  9. GtkWidget *btn;
  10. gtk_init(&argc, &argv);
  11. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  12. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  13. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  14. gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  15. gtk_window_set_title(GTK_WINDOW(window), "Enter signal");
  16. halign = gtk_alignment_new(0, 0, 0, 0);
  17. btn = gtk_button_new_with_label("Button");
  18. gtk_widget_set_size_request(btn, 70, 30);
  19. gtk_container_add(GTK_CONTAINER(halign), btn);
  20. gtk_container_add(GTK_CONTAINER(window), halign);
  21. g_signal_connect(G_OBJECT(btn), "enter",
  22. G_CALLBACK(enter_button), NULL);
  23. g_signal_connect(G_OBJECT(window), "destroy",
  24. G_CALLBACK(gtk_main_quit), NULL);
  25. gtk_widget_show_all(window);
  26. gtk_main();
  27. return 0;
  28. }

在该示例中,当我们将鼠标指针悬停在其上方时,按钮小部件的背景颜色会更改。

  1. g_signal_connect(G_OBJECT(btn), "enter",
  2. G_CALLBACK(enter_button), NULL);

enter信号出现时,我们调用enter_button()用户功能。

  1. void enter_button(GtkWidget *widget, gpointer data) {
  2. GdkColor col = {0, 27000, 30000, 35000};
  3. gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &col);
  4. }

在回调内部,我们通过调用gtk_widget_modify_bg()函数来更改按钮的背景。

断开回调

我们可以从信号断开回调。 下一个代码示例演示了这种情况。

disconnect.c

  1. #include <gtk/gtk.h>
  2. gint handler_id;
  3. void button_clicked(GtkWidget *widget, gpointer data) {
  4. g_print("clicked\n");
  5. }
  6. void toogle_signal(GtkWidget *widget, gpointer window) {
  7. if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
  8. handler_id = g_signal_connect(G_OBJECT(window), "clicked",
  9. G_CALLBACK(button_clicked), NULL);
  10. } else {
  11. g_signal_handler_disconnect(window, handler_id);
  12. }
  13. }
  14. int main(int argc, char *argv[]) {
  15. GtkWidget *window;
  16. GtkWidget *hbox;
  17. GtkWidget *vbox;
  18. GtkWidget *btn;
  19. GtkWidget *cb;
  20. gtk_init(&argc, &argv);
  21. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  22. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  23. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  24. gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  25. gtk_window_set_title(GTK_WINDOW(window), "Disconnect");
  26. hbox = gtk_hbox_new(FALSE, 15);
  27. btn = gtk_button_new_with_label("Click");
  28. gtk_widget_set_size_request(btn, 70, 30);
  29. gtk_box_pack_start(GTK_BOX(hbox), btn, FALSE, FALSE, 0);
  30. cb = gtk_check_button_new_with_label("Connect");
  31. gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE);
  32. gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);
  33. vbox = gtk_vbox_new(FALSE, 5);
  34. gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  35. gtk_container_add(GTK_CONTAINER(window), vbox);
  36. handler_id = g_signal_connect(G_OBJECT(btn), "clicked",
  37. G_CALLBACK(button_clicked), NULL);
  38. g_signal_connect(G_OBJECT(cb), "clicked",
  39. G_CALLBACK(toogle_signal), (gpointer) btn);
  40. g_signal_connect(G_OBJECT(window), "destroy",
  41. G_CALLBACK(gtk_main_quit), NULL);
  42. gtk_widget_show_all(window);
  43. gtk_main();
  44. return 0;
  45. }

在代码示例中,我们有一个按钮和一个复选框。 复选框用于从按钮的clicked信号连接或断开回调。

  1. handler_id = g_signal_connect(G_OBJECT(btn), "clicked",
  2. G_CALLBACK(button_clicked), NULL);

g_signal_connect()返回唯一标识回调的处理器 ID。

  1. if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
  2. handler_id = g_signal_connect(G_OBJECT(window), "clicked",
  3. G_CALLBACK(button_clicked), NULL);
  4. } else {
  5. g_signal_handler_disconnect(window, handler_id);
  6. }

此代码确定复选框的状态。 根据状态,它通过g_signal_connect()函数连接回调或通过g_signal_handler_disconnect()函数断开连接。

GTK  事件和信号 - 图2

图:断开连接

拖放示例

在下一个示例中,我们显示无边界窗口,并学习如何拖动和移动这样的窗口。

dragdrop.c

  1. #include <gtk/gtk.h>
  2. gboolean on_button_press(GtkWidget* widget,
  3. GdkEventButton *event, GdkWindowEdge edge) {
  4. if (event->type == GDK_BUTTON_PRESS) {
  5. if (event->button == 1) {
  6. gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  7. event->button,
  8. event->x_root,
  9. event->y_root,
  10. event->time);
  11. }
  12. }
  13. return TRUE;
  14. }
  15. int main(int argc, char *argv[]) {
  16. GtkWidget *window;
  17. gtk_init(&argc, &argv);
  18. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  19. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  20. gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  21. gtk_window_set_title(GTK_WINDOW(window), "Drag & drop");
  22. gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
  23. gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
  24. g_signal_connect(G_OBJECT(window), "button-press-event",
  25. G_CALLBACK(on_button_press), NULL);
  26. g_signal_connect(G_OBJECT(window), "destroy",
  27. G_CALLBACK(gtk_main_quit), G_OBJECT(window));
  28. gtk_widget_show(window);
  29. gtk_main();
  30. return 0;
  31. }

该示例演示了无边界窗口的拖放操作。

  1. gtk_window_set_decorated(GTK_WINDOW(window), FALSE);

我们使用gtk_window_set_decorated()函数删除窗口装饰。 这意味着该窗口将没有边框和标题栏。

  1. g_signal_connect(G_OBJECT(window), "button-press-event",
  2. G_CALLBACK(on_button_press), NULL);

我们将窗口连接到button-press-event信号。

  1. gboolean on_button_press(GtkWidget* widget,
  2. GdkEventButton *event, GdkWindowEdge edge) {
  3. if (event->type == GDK_BUTTON_PRESS) {
  4. if (event->button == 1) {
  5. gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
  6. event->button,
  7. event->x_root,
  8. event->y_root,
  9. event->time);
  10. }
  11. }
  12. return TRUE;
  13. }

on_button_press()函数内,我们执行拖放操作。 我们检查是否按下了鼠标左键。 然后我们调用gtk_window_begin_move_drag()函数,该函数开始移动窗口。

计时器示例

下面的示例演示一个计时器示例。 当我们有一些重复的任务时使用计时器。 可能是时钟,倒数,视觉效果或动画。

timer.c

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. gchar buf[256];
  4. gboolean on_expose_event(GtkWidget *widget,
  5. GdkEventExpose *event,
  6. gpointer data) {
  7. cairo_t *cr;
  8. cr = gdk_cairo_create(widget->window);
  9. cairo_move_to(cr, 30, 30);
  10. cairo_set_font_size(cr, 15);
  11. cairo_show_text(cr, buf);
  12. cairo_destroy(cr);
  13. return FALSE;
  14. }
  15. gboolean time_handler(GtkWidget *widget) {
  16. if (widget->window == NULL) return FALSE;
  17. GDateTime *now = g_date_time_new_now_local();
  18. gchar *my_time = g_date_time_format(now, "%H:%M:%S");
  19. g_sprintf(buf, "%s", my_time);
  20. g_free(my_time);
  21. g_date_time_unref(now);
  22. gtk_widget_queue_draw(widget);
  23. return TRUE;
  24. }
  25. int main(int argc, char *argv[]) {
  26. GtkWidget *window;
  27. GtkWidget *darea;
  28. gtk_init(&argc, &argv);
  29. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  30. darea = gtk_drawing_area_new();
  31. gtk_container_add(GTK_CONTAINER(window), darea);
  32. g_signal_connect(darea, "expose-event",
  33. G_CALLBACK(on_expose_event), NULL);
  34. g_signal_connect(window, "destroy",
  35. G_CALLBACK(gtk_main_quit), NULL);
  36. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  37. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  38. gtk_window_set_title(GTK_WINDOW(window), "Timer");
  39. g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);
  40. gtk_widget_show_all(window);
  41. time_handler(window);
  42. gtk_main();
  43. return 0;
  44. }

该示例在窗口上显示当前本地时间。 也使用 Cairo 2D 库。

  1. g_signal_connect(darea, "expose-event",
  2. G_CALLBACK(on_expose_event), NULL);

我们在on_expose_event()回调中绘制时间。 回调连接到expose-event信号,该信号在要重绘窗口时发出。

  1. g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);

此功能注册计时器。 time_handler()函数会定期重复调用。 就我们而言,每一秒钟。 将调用计时器函数,直到返回FALSE

  1. time_handler(window);

这将立即调用计时器函数。 否则,将延迟一秒钟。

  1. cairo_t *cr;
  2. cr = gdk_cairo_create(widget->window);
  3. cairo_move_to(cr, 30, 30);
  4. cairo_set_font_size(cr, 15);
  5. cairo_show_text(cr, buf);
  6. cairo_destroy(cr);

此代码在窗口上绘制当前时间。 有关 Cairo 2D 库的更多信息,请参见 ZetCode 的 Cairo 图形教程

  1. if (widget->window == NULL) return FALSE;

当窗口被破坏时,可能会调用计时器函数。 这行代码可以防止处理已经销毁的窗口小部件。

  1. GDateTime *now = g_date_time_new_now_local();
  2. gchar *my_time = g_date_time_format(now, "%H:%M:%S");
  3. g_sprintf(buf, "%s", my_time);

这些行确定当前的本地时间。 时间存储在全局buf变量中。

  1. gtk_widget_queue_draw(widget);

gtk_widget_queue_draw()函数使窗口区域无效,然后发出expose-event信号。

本章是关于 GTK+ 中的事件的。