当前位置: 首页 > news >正文

1 引言(1.1 - 1.5)

1 引言
GTK 是一个控件工具包。使用 GTK 创建的每个用户界面都由控件(Widget)组成。这是通过 C 语言结合 GObject(一个适用于 C 语言的面向对象框架)实现的。控件按层级结构组织,窗口控件是主要的容器。用户界面的构建方式是:向窗口中添加按钮、下拉菜单、输入框和其他控件。如果要创建复杂的用户界面,建议使用 GtkBuilder 及其特定于 GTK 的标记描述语言,而非手动组装界面。你也可以使用可视化用户界面编辑器,例如 glade。
GTK 是事件驱动的。该工具包会监听诸如按钮点击之类的事件,并将事件传递给你的应用程序。
本章包含一些入门教程内容,帮助你开始 GTK 编程。本章假定你已安装好 GTK 及其依赖项,以及 C 编译器,并且它们均可正常使用。如果需要先构建 GTK 本身,请参考本手册中的“编译 GTK 库”部分。

1.1 GTK 入门
为了开启对 GTK 的介绍,我们将从一个非常简单的应用程序入手。这个程序会创建一个 200 × 200 像素的空窗口。

01

图 1 一个窗口

创建一个新文件,内容如下,并命名为example-0.c.

#include <gtk/gtk.h>static void
activate (GtkApplication* app,gpointer        user_data)
{GtkWidget *window;window = gtk_application_window_new (app);gtk_window_set_title (GTK_WINDOW (window), "Window");gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);gtk_widget_show (window);
}int
main (int    argc,char **argv)
{GtkApplication *app;int status;app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);status = g_application_run (G_APPLICATION (app), argc, argv);g_object_unref (app);return status;
}

你可以使用 GCC 编译上述代码,命令如下:
gcc pkg-config --cflags gtk4 -o example-0 example-0.c pkg-config --libs gtk4
有关如何编译 GTK 应用程序的更多信息,请参考本手册中的编译 GTK 应用程序部分。
当然,所有 GTK 应用程序都会包含 gtk/gtk.h,该头文件声明了 GTK 应用程序所需的函数、类型和宏。
即使 GTK 安装了多个头文件,第三方代码也只能直接包含顶层的 gtk/gtk.h 头文件。如果直接包含其他任何头文件,编译器都会报错并终止编译。
在 GTK 应用程序中,main() 函数的作用是创建一个 GtkApplication 对象并运行它。在这个示例中,声明了一个名为 app 的 GtkApplication 指针,然后使用 gtk_application_new() 对其进行初始化。
创建 GtkApplication 时,你需要选择一个应用程序标识符(名称),并将其作为参数传递给 gtk_application_new()。本示例中使用的是 org.gtk.example。关于如何为你的应用程序选择标识符,请参考此指南。此外,如果你的应用程序有特殊需求,gtk_application_new() 还会接收 GApplicationFlags 作为输入参数。
接下来,将 activate信号连接到 main() 函数中的 activate() 函数。当代码中的 g_application_run() 运行时,会触发 activate 信号。g_application_run() 调用还接收命令行参数(计数 argc 和字符串数组 argv)作为参数。你的应用程序可以覆盖命令行处理方式,例如打开命令行中传递的文件。
在 g_application_run() 中,activate 信号被发送,随后我们进入应用程序的 activate() 函数。这里是构建 GTK 窗口的地方,以便在应用程序启动时显示一个窗口。调用 gtk_application_window_new() 将创建一个新的 GtkWindow,并将其存储在 window 指针中。该窗口会有一个边框、一个标题栏,以及取决于平台的窗口控件。
使用 gtk_window_set_title() 设置窗口标题。该函数接收一个 GtkWindow* 指针和一个字符串作为输入。由于我们的 window 指针是一个 GtkWidget 指针,因此需要将其转换为 GtkWindow。但无需通过 (GtkWindow) 进行转换,而是可以使用宏 GTK_WINDOW() 来转换 window。GTK_WINDOW() 会在转换前检查该指针是否为 GtkWindow 类的实例,如果检查失败则发出警告。有关此约定的更多信息可在此处找到。
最后,使用 gtk_window_set_default_size()设置窗口大小,然后通过 GTK 的 gtk_widget_show() 显示窗口。
当你关闭窗口(例如按下 X 按钮)时,g_application_run() 调用会返回一个数值,该数值被保存在名为 status 的整数变量中。之后,通过 g_object_unref() 释放 GtkApplication 对象所占的内存。最后返回 status 整数,应用程序退出。
程序运行时,GTK 会接收事件。这些事件通常是用户与程序交互产生的输入事件,但也包括来自窗口管理器或其他应用程序的消息等。GTK 处理这些事件,结果可能会在你的控件上触发信号。连接这些信号的处理器是使程序响应用户输入的常用方式。
下面的示例稍微复杂一些,尝试展示 GTK 的部分功能。

1.2 Hello, World
在编程语言和库的悠久传统中,这个示例被称为“Hello, World”(你好,世界)。

02

图 2 Hello, world

.C 的 Hello World
创建一个新文件,内容如下,并命名为 example-1.c。

#include <gtk/gtk.h>static void
print_hello (GtkWidget *widget,gpointer   data)
{g_print ("Hello World\n");
}static void
activate (GtkApplication *app,gpointer        user_data)
{GtkWidget *window;GtkWidget *button;GtkWidget *box;window = gtk_application_window_new (app);gtk_window_set_title (GTK_WINDOW (window), "Window");gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);gtk_window_set_child (GTK_WINDOW (window), box);button = gtk_button_new_with_label ("Hello World");g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);gtk_box_append (GTK_BOX (box), button);gtk_widget_show (window);
}int
main (int    argc,char **argv)
{GtkApplication *app;int status;app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);status = g_application_run (G_APPLICATION (app), argc, argv);g_object_unref (app);return status;
}

你可以使用 GCC 编译上述程序,命令如下:
gcc pkg-config --cflags gtk4 -o example-1 example-1.c pkg-config --libs gtk4
如前所述,example-1.c 在 example-0.c 的基础上进行了扩展,在窗口中添加了一个标签为“Hello World”的按钮。为实现这一功能,声明了两个新的 GtkWidget 指针:button 和 box。创建 box 变量是为了存储 GtkBox,这是 GTK 中用于控制按钮大小和布局的方式。
GtkBox 通过调用 gtk_box_new() 创建,该函数接收一个 GtkOrientation 枚举作为参数。此box将要包含的按钮可以按水平或垂直方向排列。在这个特定示例中,这一点并不重要,因为我们只处理一个按钮。用新创建的 GtkBox 初始化 box 后,代码使用 gtk_window_set_child() 将 box 控件添加到窗口控件中。
接下来,以类似的方式初始化 button 变量。调用 gtk_button_new_with_label(),其返回一个 GtkButton 并存储在 button 中。之后,将 button 添加到我们的 box 中。
通过 g_signal_connect(),将按钮与应用中的 print_hello() 函数相连,这样当按钮被点击时,GTK 就会调用该函数。由于 print_hello() 函数不需要任何数据作为输入,因此向其传递 NULL。print_hello() 调用带有字符串“Hello World”的 g_print(),如果 GTK 应用程序是从终端启动的,这将在终端中打印“Hello World”。
连接 print_hello() 之后,使用 g_signal_connect_swapped() 将另一个信号连接到按钮的“clicked”状态。该函数与 g_signal_connect() 类似,不同之处在于对回调函数的处理方式。g_signal_connect_swapped() 允许你通过将参数作为数据传递来指定回调函数应接收的参数。在这种情况下,被回调的函数是 gtk_window_destroy(),并且将 window 指针传递给它。这样,当按钮被点击时,整个 GTK 窗口都会被销毁。相比之下,如果使用普通的 g_signal_connect() 将“clicked”信号与 gtk_window_destroy() 连接,那么该函数会作用于 button(这会出问题,因为该函数期望接收一个 GtkWindow 作为参数)。
有关创建按钮的更多信息可在此处查看。
example-1.c 中的其余代码与 example-0.c 相同。下一节将进一步详细说明如何向 GTK 应用程序中添加多个 GtkWidget。

1.3 布局排列
在创建应用程序时,你可能希望在一个窗口中放置多个控件。这时,控制每个控件的位置和大小就变得非常重要,而布局排列(packing)正是用于解决这个问题的。
GTK 提供了多种布局容器,它们的作用是控制添加到其中的子控件的布局方式。有关概述,请参见布局容器部分。
下面的示例展示了如何使用 GtkGrid 容器来排列多个按钮:

03

图 3 网格排列

添加按钮控件
创建一个新文件,内容如下,并命名为 example-2.c。

#include <gtk/gtk.h>static void
print_hello (GtkWidget *widget,gpointer   data)
{g_print ("Hello World\n");
}static void
activate (GtkApplication *app,gpointer        user_data)
{GtkWidget *window;GtkWidget *grid;GtkWidget *button;/* create a new window, and set its title */window = gtk_application_window_new (app);gtk_window_set_title (GTK_WINDOW (window), "Window");/* Here we construct the container that is going pack our buttons */grid = gtk_grid_new();/* Pack the container in the window */gtk_window_set_child (GTK_WINDOW (window), grid);button = gtk_button_new_with_label ("Button 1");g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);/* Place the first button in the grid cell (0, 0), and make it fill* just 1 cell horizontally and vertically (ie no spanning)*/gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);button = gtk_button_new_with_label ("Button 2");g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);/* Place the second button in the grid cell (1, 0), and make it fill* just 1 cell horizontally and vertically (ie no spanning)*/gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 1);button = gtk_button_new_with_label ("Quit");g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window);/* Place the Quit button in the grid cell (0, 1), and make it* span 2 columns.*/gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 2, 1);gtk_widget_show (window);}int
main (int    argc,char **argv)
{GtkApplication *app;int status;app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);status = g_application_run (G_APPLICATION (app), argc, argv);g_object_unref (app);return status;
}

你可以使用 GCC 编译上述程序,命令如下:
gcc pkg-config --cflags gtk4-o example-2 example-2.cpkg-config --libs gtk4``

1.4 自定义绘制
许多控件(如按钮)会自行完成所有绘制工作。你只需告诉它们想要显示的标签,它们就会自行处理字体选择、按钮轮廓绘制、焦点矩形绘制等操作。但有时,我们需要进行一些自定义绘制。这种情况下,GtkDrawingArea 可能是合适的控件选择。它提供了一个画布,你可以通过连接 ::draw 信号在上面进行绘制。
控件的内容常常需要部分或完全重绘,例如当另一个窗口被移开而露出该控件的一部分时,或者当包含该控件的窗口被调整大小时。你也可以通过调用 gtk_widget_queue_draw() 或其变体函数,显式地使控件的部分或全部区域重绘。GTK 会通过向 ::draw 信号处理程序提供一个现成可用的 cairo 上下文,来处理大部分细节工作。
下面的示例展示了一个 ::draw 信号处理程序。它比前面的示例稍微复杂一些,因为它还通过事件控制器演示了输入事件处理。

04

图 4 绘制

响应输入绘制
创建一个新文件,内容如下,并命名为 example-4.c。

#include <gtk/gtk.h>/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;static void
clear_surface (void)
{cairo_t *cr;cr = cairo_create (surface);cairo_set_source_rgb (cr, 1, 1, 1);cairo_paint (cr);cairo_destroy (cr);
}/* Create a new surface of the appropriate size to store our scribbles */
static void
resize_cb (GtkWidget *widget,int        width,int        height,gpointer   data)
{if (surface){cairo_surface_destroy (surface);surface = NULL;}if (gtk_native_get_surface (gtk_widget_get_native (widget))){surface = gdk_surface_create_similar_surface (gtk_native_get_surface (gtk_widget_get_native (widget)),CAIRO_CONTENT_COLOR,gtk_widget_get_width (widget),gtk_widget_get_height (widget));/* Initialize the surface to white */clear_surface();}
}/* Redraw the screen from the surface. Note that the draw* callback receives a ready-to-be-used cairo_t that is already* clipped to only draw the exposed areas of the widget*/
static void
draw_cb (GtkDrawingArea *drawing_area,cairo_t        *cr,int             width,int             height,gpointer        data)
{cairo_set_source_surface (cr, surface, 0, 0);cairo_paint (cr);
}/* Draw a rectangle on the surface at the given position */
static void
draw_brush (GtkWidget *widget,double     x,double     y)
{cairo_t *cr;/* Paint to the surface, where we store our state */cr = cairo_create (surface);cairo_rectangle (cr, x - 3, y - 3, 6, 6);cairo_fill (cr);cairo_destroy (cr);/* Now invalidate the drawing area. */gtk_widget_queue_draw (widget);
}static double start_x;
static double start_y;static void
drag_begin (GtkGestureDrag *gesture,double          x,double          y,GtkWidget      *area)
{start_x = x;start_y = y;draw_brush (area, x, y);
}static void
drag_update (GtkGestureDrag *gesture,double          x,double          y,GtkWidget      *area)
{draw_brush (area, start_x + x, start_y + y);
}static void
drag_end (GtkGestureDrag *gesture,double          x,double          y,GtkWidget      *area)
{draw_brush (area, start_x + x, start_y + y);
}static void
pressed (GtkGestureClick *gesture,int              n_press,double           x,double           y,GtkWidget       *area)
{clear_surface();gtk_widget_queue_draw (area);
}static void
close_window (void)
{if (surface)cairo_surface_destroy (surface);
}static void
activate (GtkApplication *app,gpointer        user_data)
{GtkWidget *window;GtkWidget *frame;GtkWidget *drawing_area;GtkGesture *drag;GtkGesture *press;window = gtk_application_window_new (app);gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);frame = gtk_frame_new (NULL);gtk_window_set_child (GTK_WINDOW (window), frame);drawing_area = gtk_drawing_area_new();/* set a minimum size */gtk_widget_set_size_request (drawing_area, 100, 100);gtk_frame_set_child (GTK_FRAME (frame), drawing_area);gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (drawing_area), draw_cb, NULL, NULL);g_signal_connect_after (drawing_area, "resize", G_CALLBACK (resize_cb), NULL);drag = gtk_gesture_drag_new();gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (drag));g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), drawing_area);g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), drawing_area);g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), drawing_area);press = gtk_gesture_click_new();gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_SECONDARY);gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (press));g_signal_connect (press, "pressed", G_CALLBACK (pressed), drawing_area);gtk_widget_show (window);
}int
main (int    argc,char **argv)
{GtkApplication *app;int status;app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);status = g_application_run (G_APPLICATION (app), argc, argv);g_object_unref (app);return status;
}

你可以使用 GCC 编译上述程序,命令如下:
gcc pkg-config --cflags gtk4-o example-4 example-4.cpkg-config --libs gtk4``

1.5 构建用户界面
当构建更复杂的用户界面(包含数十或数百个控件)时,在 C 代码中完成所有设置工作会非常繁琐,而且修改起来几乎难以实现。
幸运的是,GTK 支持将用户界面布局与业务逻辑分离,具体方式是使用可由 GtkBuilder 类解析的 XML 格式的界面描述。
使用 GtkBuilder 排列按钮
创建一个新文件,内容如下,并命名为 example-3.c。

#include <gtk/gtk.h>
#include <glib/gstdio.h>static void
print_hello (GtkWidget *widget,gpointer   data)
{g_print ("Hello World\n");
}static void
quit_cb (GtkWindow *window)
{gtk_window_close (window);
}static void
activate (GtkApplication *app,gpointer        user_data)
{/* Construct a GtkBuilder instance and load our UI description */GtkBuilder *builder = gtk_builder_new();gtk_builder_add_from_file (builder, "builder.ui", NULL);/* Connect signal handlers to the constructed widgets. */GObject *window = gtk_builder_get_object (builder, "window");gtk_window_set_application (GTK_WINDOW (window), app);GObject *button = gtk_builder_get_object (builder, "button1");g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);button = gtk_builder_get_object (builder, "button2");g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);button = gtk_builder_get_object (builder, "quit");g_signal_connect_swapped (button, "clicked", G_CALLBACK (quit_cb), window);gtk_widget_show (GTK_WIDGET (window));/* We do not need the builder any more */g_object_unref (builder);
}int
main (int   argc,char *argv[])
{
#ifdef GTK_SRCDIRg_chdir (GTK_SRCDIR);
#endifGtkApplication *app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);int status = g_application_run (G_APPLICATION (app), argc, argv);g_object_unref (app);return status;
}

创建一个新文件,内容如下,并命名为 builder.ui。

<?xml version="1.0" encoding="UTF-8"?>
<interface><object id="window" class="GtkWindow"><property name="title">Grid</property><child><object id="grid" class="GtkGrid"><child><object id="button1" class="GtkButton"><property name="label">Button 1</property><layout><property name="column">0</property><property name="row">0</property></layout></object></child><child><object id="button2" class="GtkButton"><property name="label">Button 2</property><layout><property name="column">1</property><property name="row">0</property></layout></object></child><child><object id="quit" class="GtkButton"><property name="label">Quit</property><layout><property name="column">0</property><property name="row">1</property><property name="column-span">2</property></layout></object></child></object></child></object>
</interface>

你可以使用 GCC 编译上述程序,命令如下:
gcc pkg-config --cflags gtk4-o example-3 example-3.cpkg-config --libs gtk4``
需要注意的是,GtkBuilder 还可用于构造非控件对象,例如树模型、调整器等。这就是我们此处使用的方法名为 gtk_builder_get_object() 且返回 GObject* 而非 GtkWidget* 的原因。
通常,你需要向 gtk_builder_add_from_file() 传递完整路径,以使程序的执行不受当前目录影响。UI 描述及类似数据的常见安装位置是 /usr/share/appname。
也可以将 UI 描述作为字符串嵌入源代码中,并使用 gtk_builder_add_from_string() 来加载它。但将 UI 描述放在单独的文件中具有多个优势:无需重新编译程序即可对界面进行小的调整,更重要的是,像 glade 这样的图形界面编辑器可以加载该文件,让你通过点击操作来创建和修改界面。

通过网盘分享的知识:GTK 4 Reference Manual
链接: https://pan.baidu.com/s/57mKoejMZPrxs_wh3a7Kj9g
--来自百度网盘超级会员v7的分享

http://www.sczhlp.com/news/801.html

相关文章:

  • 支持向量机算法
  • 决策树算法
  • 逻辑回归算法
  • static关键字--main函数
  • 长文!推荐‑搜索‑广告系统评估指标与损失函数技术报告
  • 集成学习算法
  • K 近邻算法
  • CVE-2020-13945 Apache APISIX 默认密钥漏洞 (复现)
  • 1 引言(1.6)
  • 可并堆(左偏树)
  • 7-28
  • DAY24
  • 2025 ZR暑假集训 CD联考 Day2 E 环球旅行
  • zk后集训
  • 乘法逆元(部分施工)、exgcd
  • 夏令营Ⅲ期
  • centos8.2 挂载本地镜像作为yum源
  • 非常值得学习渲染入门的一个教程
  • HDU 多校 2025 R3
  • 7.28SAM后缀自动机,回文自动机
  • Linux开机自动登录的一种方法
  • day5
  • JAVA语言学习总结(第27天)
  • CVE-2021-45232 Apache APISIX Dashboard身份验证绕过漏洞 (复现)
  • IIS中配置HTTPS证书的详细步骤
  • Python入门学习(七)高级部分:正则表达式
  • 在运维工作中,如果运行的一个容器突然挂了,如何排查?
  • SciTech-EECS-Library: img2pdf 与 pdf2image : Python 的 pdf 与 image 双向转换库
  • 在运维工作中,docker封闭了哪些资源?
  • 深度学习(pytorch量化)