常见问题 [源]

这是一个按常见“如何…”问题组织的参考资料手册的“索引”。如果不确定要阅读哪个文档来回答你的问题,这个列表是一个很好的起点。

一般问题

  • 如何开始使用GTK

    GTK 网站 提供了一些 教程 和其他文档。此参考资料手册还包括一个介绍性的 入门 部分。

    从白皮书到在线书籍,更多的文档可以在GNOME 开发者网站 找到。在学习了这些材料后,你应该可以为详细信息返回此参考资料手册做好准备。

  • 我在哪里可以获得关于GTK的帮助,提交一个错误报告或提出一个特性请求?

    请参阅此文档

  • 如何将应用程序从一个GTK版本迁移到另一个版本?

    每个主要的GTK版本都附带一个迁移指南。您也可能在特定小部件和功能的文档中找到有用的信息。如果您有手册中没有涉及的问题,请随时提问,并可提交错误报告针对文档。

  • 我应该在我的GTK x 和 GTK y 中维护并行的UI版本吗?

    最终,这取决于你。

    我们的经验是,这是一项工作量很大的工作,通常不是一个好主意。

    如果你还没有准备好跳到下一个主要版本的GTK,坚持使用稳定的发布版本是完全正常的。我们维护它们就是这个原因。

  • GTK中的内存管理是如何工作的?我应该释放函数返回的数据吗?

    请参阅GObjectGInitiallyUnowned 的文档。对于GObject,请注意特定的 g_object_ref()g_object_unref()。由于GInitiallyUnownedGObject的子类,所以相同的点适用,但它是“浮动”状态(在它的文档中解释了)。

    在小部件树中,每个容器都拥有其子级的一个引用。根对象(通常是GtkWindow)由GTK所有。当你调用gtk_window_destroy()时,GTK将放弃其引用。

    从函数返回的字符串,如果不应该释放,则将声明为“const”。非-const字符串应使用g_free()释放。数组遵循相同的规则。如果您发现规则中的未记录异常,请提交错误报告

    文档中的gobject-introspection传输注释可以提供关于内存处理语义的有用提示。

  • 为什么当我立即在创建小部件后销毁它时,我的程序会泄露内存?

    如果GtkFoo不是一个顶级窗口,那么

    foo = gtk_foo_new ();
    g_object_unref (foo);
    

    是一个内存泄漏,因为没有人为初始的浮动引用做出假设(你也会收到关于此的警告)。如果你使用一个小部件并且没有立即将其包装到一个容器中,那么你可能想要标准的引用计数,而不是浮动引用计数。

    为此,你必须在小部件创建后获取对其的引用并释放浮动引用(在GObject术语中为ref and sink)。

    foo = gtk_foo_new ();
    g_object_ref_sink (foo);
    

    当您立即将小部件添加到容器中时,它会自动处理初始浮动参考,您无需担心引用计数……只需从容器中移除小部件即可删除它。

  • 我该如何使用线程中的 GTK

    GTK 要求所有 GTK API 调用都必须在创建 GtkApplication 或调用 gtk_init() 的同一个线程中执行(即主线程)。

    如果您想在 GTK 应用程序中利用多线程,通常最好将长时间运行的任务发送到工作线程,并通过 g_idle_add()GAsyncQueue 将结果反馈到主线程。 GIO 提供了一些有用的工具来实现此方法,例如 GTask

  • 我该如何国际化 GTK 程序?

    大多数人使用的是 GNU gettext,这是安装 GLib 所必需的。在一个已安装 gettext 的 UNIX 或 Linux 系统上,键入 info gettext 以阅读文档。

    关于如何使用 gettext 的简短清单如下:调用 bindtextdomain() 以让 gettext 能够找到包含您的翻译文件,调用 textdomain() 设置默认翻译域,调用 bind_textdomain_codeset() 要求所有翻译字符串以 UTF-8 格式返回,然后调用 gettext() 在默认域中查找要翻译的每个字符串。

    gi18n.h 提供了一些简化的宏以方便使用。传统上,人们如下定义宏以方便使用:

    #define  _(x)     gettext (x)
    #define N_(x)     x
    #define C_(ctx,x) pgettext (ctx, x)
    

    您使用 N_()(N 代表 no-op)来标记一个字符串以便在不允许函数调用 gettext() 的位置进行翻译,例如在数组初始化器中。您最终必须对字符串调用 gettext() 以实际上获取翻译。 _() 既可以标记字符串以进行翻译,也可以实际上翻译该字符串。 C_() 宏(C 代表上下文)为要翻译的字符串添加额外的上下文,这可以帮助区分短字符串,在程序的不同部分可能需要不同的翻译。

    使用这些宏的代码看起来像这样

    #include <gi18n.h>
    
    static const char *global_variable = N_("Translate this string");
    
    static void
    make_widgets (void)
    {
      GtkWidget *label1;
      GtkWidget *label2;
    
      label1 = gtk_label_new (_("Another string to translate"));
      label2 = gtk_label_new (_(global_variable));
    ...
    

    使用 gettext 的库应使用 dgettext() 而不是 gettext(),这样它们可以每次请求翻译时指定翻译域。库还应避免调用 textdomain(),因为它们将指定域而不是使用默认的。

    按照约定,宏 GETTEXT_PACKAGE 被定义为包含您的库翻译域,gi18n-lib.h 可以包含以提供以下方便:

    #define _(x) dgettext (GETTEXT_PACKAGE, x)
    
  • 我在 GTK 程序中使用非 ASCII 字符?

    GTK 使用 Unicode(更准确地说是 UTF-8)来处理所有文本。UTF-8 将每个 Unicode 代码点编码为一到六个字节的序列,具有许多优良的属性,这使得它非常适合在 C 程序中处理 Unicode 文本。

    • ASCII 字符使用它们熟悉的 ASCII 代码点进行编码。
    • ASCII 字符永远不会作为任何其他字符的一部分出现。
    • 零字节不会作为字符的一部分出现,因此可以使用处理零终止字符串的常规 C 库函数操作 UTF-8 字符串。

    有关 Unicode 和 UTF-8 的更多信息,请参阅 UTF-8 和 Unicode FAQ。GLib 提供了在 UTF-8 和其他编码之间转换字符串的函数,请参阅 g_locale_to_utf8()g_convert()

    来自外部来源(例如文件或用户输入)的文本,在传送给 GTK 之前,必须转换为 UTF-8 编码。以下示例将 ISO-8859-1 编码的文本文件的 内容写入 stdout

    char *text, *utf8_text;
    gsize length;
    GError *error = NULL;
    
    if (g_file_get_contents (filename, &amp;text, &amp;length, NULL))
      {
        utf8_text = g_convert (text, length, "UTF-8", "ISO-8859-1",
                               NULL, NULL, &error);
        if (error != NULL)
          {
            fprintf ("Couldn't convert file %s to UTF-8\n", filename);
            g_error_free (error);
          }
        else
          g_print (utf8_text);
      }
    else
      fprintf (stderr, "Unable to read file %s\n", filename);
    

    在源代码中的字符串字面量中,有几种处理非 ASCII 内容的替代方法

    • 直接 UTF-8

    如果您的编辑器和编译器可以处理 UTF-8 编码的源代码,那么简单地在字符串字面量中使用 UTF-8 将非常方便,因为这允许您以“所见即所得”的方式编辑字符串。请注意,选择此选项可能会降低您代码的移植性。

    • 转义 UTF-8

    即使您的工具链无法直接处理 UTF-8,您仍然可以通过使用八进制或十六进制转义符(如 \212\xa8)来将字符串字面量编码为 UTF-8。这是可移植的,但修改转义字符串并不方便。注意在混合十六进制转义符和普通文本时要小心;"\xa8abcd" 是一个长度为1的字符串 !

    • 运行时转换

    如果字符串字面量可以用您的工具链可以处理的编码表示(例如,IS0-8859-1),您可以将源代码文件写成该编码,并使用 g_convert() 在运行时将字符串转换为 UTF-8。请注意,这会有一些运行时开销,因此您可能希望将转换从内部循环中移出。

    以下示例展示了使用版权符号 © 的三种方法,该符号具有 Unicode 和 ISO-8859-1 码点 169,而在 UTF-8 中用两个字节 194, 169 表示,或作为字符串字面量表示为 "\302\251"

    g_print ("direct UTF-8: ©");
    g_print ("escaped UTF-8: \302\251");
    text = g_convert ("runtime conversion: ©", -1,
                      "ISO-8859-1", "UTF-8", NULL, NULL, NULL);
    g_print (text);
    g_free (text);
    

    如果您使用 gettext() 来本地化您的应用程序,您需要调用 bind_textdomain_codeset() 以确保返回的翻译字符串以 UTF-8 编码。

  • 如何使用 C++ 与 GTK 进行交互?

    有两条途径可以处理这个问题。由于 GTK 头文件使用的是有效的 C++ 的 C 子集,所以您可以在 C++ 程序中简单地使用正常的 GTK API。或者,您可以使用“C++ 绑定”,例如gtkmm,它提供了一个本地的 C++ API

    当直接使用 GTK 时,请记住只有函数可以连接到信号,不能是方法。因此,您将需要使用全局函数或静态类函数来进行信号连接。

    在直接使用 GTK 时,另一个常见问题是 C++ 不会隐式地将整数转换为枚举。这在使用位域时会遇到;在 C 中,您可以写出以下代码

    gdk_surface_set_events (gdk_surface,
                            GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
    

    而在 C++ 中,必须写出

    gdk_surface_set_events (gdk_surface,
                            (GdkEventMask) GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
    

    需要进行这种转换的函数非常少。

  • 如何使用 GTK 与其他非 C 语言进行交互?

    请参阅 GTK 网站 上的 语言绑定列表

  • 如何从文件中加载图像或动画?

    要将图像文件直接加载到显示小部件中,请使用gtk_picture_new_for_file()gtk_picture_new_for_filename()。如果要加载图像执行其他任务,请使用gdk_texture_new_from_file()。要从文件中加载视频,请使用gtk_media_file_new_for_file()

    请注意,GtkImage主要用于固定大小的图标。对于任意大小的图像文件,你应该使用GtkPicture

  • 我如何绘制文本?

    如果你只是想在用户界面中的某个地方放置文本,通常最简单的方法是使用专为这个目的制作的现成小部件,例如GtkLabel

    要在cairo表面上绘制一段文本,请使用Pango布局和pango_cairo_show_layout()

    layout = gtk_widget_create_pango_layout (widget, text);
    fontdesc = pango_font_description_from_string ("Luxi Mono 12");
    pango_layout_set_font_description (layout, fontdesc);
    pango_cairo_show_layout (cr, layout);
    pango_font_description_free (fontdesc);
    g_object_unref (layout);
    

    请参阅Cairo渲染部分的Pango文档

    要在小部件的Gtk.WidgetClass.snapshot实现中绘制文本,请使用gtk_snapshot_append_layout()

  • 我如何测量文本片段的大小?

    为了获取文本片段的大小,请使用Pango布局和pango_layout_get_pixel_size(),以下代码示例

    layout = gtk_widget_create_pango_layout (widget, text);
    fontdesc = pango_font_description_from_string ("Luxi Mono 12");
    pango_layout_set_font_description (layout, fontdesc);
    pango_layout_get_pixel_size (layout, &amp;width, &amp;height);
    pango_font_description_free (fontdesc);
    g_object_unref (layout);
    

    请参阅布局对象部分的Pango文档

  • 为什么在用GTK_TYPE_BLAH宏使用时没有注册类型?

    GTK_TYPE_BLAH宏定义为调用gtk_blah_get_type()的表达式,并且_get_type()函数声明为G_GNUC_CONST,这使得编译器可以优化掉调用,如果它看起来该值没有被使用的话。

    GLib提供了g_type_ensure()函数来解决这个问题。

    g_type_ensure (GTK_TYPE_BLAH);
    
  • 我如何创建一个透明的顶级窗口?

    任何顶级窗口都可以是透明的。这只是一个在它的CSS样式中设置透明背景的问题。

我应该使用哪个小部件...

  • ……来显示列表和树?

    这个问题的答案因数据集的大小和所需的格式灵活性而异。

    如果你想以统一的方式显示大量数据,最佳选择是一个GtkListView小部件。列表视图可以有不同的模型类型:GtkTreeListModel可以作为树结构的模型,而一个简单的GListModel可以用于简单列表。有关启动的帮助,请参阅列表小部件概述。它替代了自GTK 4.10以来已经过时的GtkTreeView

    如果你想要显示少量条目,但是在列表中需要灵活的格式化和小部件,那么你可能会想使用一个GtkListBox,它使用常规小部件进行显示。

  • ……用于多行文本显示或编辑?

    请参阅文本小部件概述——你应该使用GtkTextView小部件。

    如果你只有少量的文本,使用GtkLabel也是合适的。你可以通过gtk_label_set_selectable()使其可选取的。对于单行文本输入,请参阅GtkEntry

  • ……显示图像或动画?

    GTK有两个专门用于显示图像的小部件。用于小、固定大小的图标的GtkImage,用于内容图像的GtkPicture

    两者均能在几乎所有 `GTK` 理解的格式下显示图像。如果您需要执行更复杂的功能,例如在图像上方绘制文本或图形,也可以使用 GtkDrawingArea

    GtkImageGtkPicture 同样可以显示动画和视频。要显示 webm 文件,请使用 GtkMediaFile `API` 加载它,然后将其用作 paintable。

    mediafile = gtk_media_file_new_for_filename ("example.webm");
    picture = gtk_picture_new_for_paintable (GDK_PAINTABLE (mediafile));
    
  • ……在哪里使用组合框来呈现一组相互排斥的选择,Windows 使用的是该方法?

    在 `GTK` 中,建议使用 GtkDropDown 小部件来实现此用例。

有关 GtkWidget 的疑问

  • 我该如何更改小部件的颜色?

    小部件的背景颜色由其应用的 `CSS` 风格决定。要更改它,您可以在小部件上设置样式类,并提供自定义 `CSS` 以更改外观。此类 `CSS` 可以通过 gtk_css_provider_load_from_file()及其实例加载。请参阅 gtk_style_context_add_provider()

  • 我该如何更改小部件的字体?

    如果您想将标签的文字放大,可以使用 gtk_label_set_markup()

    gtk_label_set_markup (label, "<big>big tex</big>");
    

    此方法在很多应用程序中更受欢迎,因为它相对于用户选择的字体大小是相对的。如果动态构造此类字符串,请参阅 g_markup_escape_text()

    您也可以通过将以下代码放入 CSS 文件中来更改小部件的字体

    .my-widget-class {
      font: Sans 30;
    }
    

    通过 gtk_css_provider_load_from_file() 加载它,并使用 gtk_style_context_add_provider_for_display() 添加提供者。要将此样式信息与小部件关联,请使用 gtk_widget_add_css_class() 在小部件上设置样式类。这种方法的优势在于用户可以覆盖您已选择的字体。有关更多讨论,请参阅 GtkStyleContext 文档。

  • 我该如何禁用/模糊/使小部件不灵敏?

    在 `GTK` 中,禁用小部件被称为 insensitive。请参阅 gtk_widget_set_sensitive()

GtkTextView 相关问题

  • 我该如何获取整个文本小部件的内容作为字符串?

    请参阅 gtk_text_buffer_get_bounds()gtk_text_buffer_get_text()gtk_text_iter_get_text()

    GtkTextIter start, end;
    GtkTextBuffer *buffer;
    char *text;
    
    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
    gtk_text_buffer_get_bounds (buffer, &start, &end);
    text = gtk_text_iter_get_text (&start, &end);
    /* use text */
    g_free (text);
    
  • 我该如何使文本小部件以特定字体显示其所有内容?

    如果您使用带有适当标记来选择字体的 gtk_text_buffer_insert_with_tags(),插入的文本将具有所需的样式,但用户在标记块之前或之后输入的文本将默认样式显示。

  • 我该如何使文本视图自动滚动到缓冲区的末尾?

    在缓冲区末尾放置标记并给予它右侧重力是一个将文本缓冲区滚动到末尾的好方法。这时文本会在标记处插入前,保持标记在末尾。

    在使用 gtk_text_view_scroll_to_mark() 将新文本插入后,使用它可以确保缓冲区末尾始终可见。

    gtk4-demo 应用程序包含这一技术的一个例子。

有关 GtkTreeView 的问题

  • 我该如何与树中的一个行关联一些数据?

    请记住,GtkTreeModel 的列不一定必须显示。因此,可以像其他数据一样将非用户可见的数据放入模型中,并使用 gtk_tree_model_get() 提取数据。请参阅 树小部件概述

  • 如何将图片和文字放在同一列中?

    您可以使用 GtkCellRenderer 将多个组件打包到单个 GtkTreeViewColumn 中,通过使用 gtk_tree_view_column_pack_start()gtk_tree_view_column_pack_end()。因此,将一个 GtkCellRendererPixbuf 和一个 GtkCellRendererText 打包到列中。

  • 我可以在我的 GtkTreeStoreGtkListStore 模型上使用 gtk_list_store_set()gtk_tree_store_set() 容易地设置数据,但不能读取它吗?

    GtkTreeStoreGtkListStore 都实现了 GtkTreeModel 接口。因此,您可以使用该接口实现的任何功能。读取一组数据的最简单方法是通过使用 gtk_tree_model_get()

  • 我该如何改变 GtkTreeView 中数字的格式方式?

    使用 gtk_tree_view_insert_column_with_data_func()gtk_tree_view_column_set_cell_data_func(),自己将数字转换为字符串(比如使用 g_strdup_printf())。

    以下示例展示了如何做到这一点。

    enum
    {
      DOUBLE_COLUMN,
      N_COLUMNS
    };
    
    GtkListStore *mycolumns;
    
    GtkTreeView *treeview;
    
    void
    my_cell_double_to_text (GtkTreeViewColumn *tree_column,
                            GtkCellRenderer   *cell,
                            GtkTreeModel      *tree_model,
                            GtkTreeIter       *iter,
                            gpointer           data)
    {
      GtkCellRendererText *cell_text = (GtkCellRendererText *)cell;
      double d;
      char *text;
    
      /* Get the double value from the model. */
      gtk_tree_model_get (tree_model, iter, (int)data, &d, -1);
      /* Now we can format the value ourselves. */
      text = g_strdup_printf ("%.2f", d);
      g_object_set (cell, "text", text, NULL);
      g_free (text);
    }
    
    void
    set_up_new_columns (GtkTreeView *myview)
    {
      GtkCellRendererText *renderer;
      GtkTreeViewColumn *column;
      GtkListStore *mycolumns;
    
      /* Create the data model and associate it with the given TreeView */
      mycolumns = gtk_list_store_new (N_COLUMNS, G_TYPE_DOUBLE);
      gtk_tree_view_set_model (myview, GTK_TREE_MODEL (mycolumns));
    
      /* Create a GtkCellRendererText */
      renderer = gtk_cell_renderer_text_new ();
    
      /* Create a new column that has a title ("Example column"),
       * uses the above created renderer that will render the double
       * value into text from the associated model's rows.
       */
      column = gtk_tree_view_column_new ();
      gtk_tree_view_column_set_title  (column, "Example column");
      renderer = gtk_cell_renderer_text_new ();
      gtk_tree_view_column_pack_start (column, renderer, TRUE);
    
      /* Append the new column after the GtkTreeView's previous columns. */
      gtk_tree_view_append_column (GTK_TREE_VIEW (myview), column);
      /* Since we created the column by hand, we can set it up for our
       * needs, e.g. set its minimum and maximum width, etc.
       */
      /* Set up a custom function that will be called when the column content
       * is rendered. We use the func_data pointer as an index into our
       * model. This is convenient when using multi column lists.
       */
      gtk_tree_view_column_set_cell_data_func (column, renderer,
                                               my_cell_double_to_text,
                                               (gpointer)DOUBLE_COLUMN, NULL);
    }
    
  • 我该如何隐藏树视图中的展开箭头?

    将树视图的展开列属性设置为一个隐藏的列。请参阅 gtk_tree_view_set_expander_column()gtk_tree_view_column_set_visible()

使用GTK中的cairo

  • 我该如何在GTK应用程序中使用cairo进行绘图?

    GtkDrawingArea 是一个用于使用cairo进行绘图的现成小部件。

    如果您实现了一个自定义小部件,请在您的 Gtk.WidgetClass.snapshot vfunc 中使用 gtk_snapshot_append_cairo() 以获取cairo上下文并使用它进行绘图。

  • 我可以通过使用cairo的另一个后端(如GL)来提高应用程序的性能吗?

    不行。在GTK中,大部分绘图不再通过cairo进行(而是通过GSK的GL或Vulkan渲染器)。

    如果您使用cairo为自己的小部件进行绘图,gtk_snapshot_append_cairo() 将为您选择最适合的表面类型。

    如果您对使用GL进行自己的绘图感兴趣,请参阅 GtkGLArea

  • 我可以用cairo在 GdkPixbuf 上绘图吗?

    不可以。cairo图像表面不支持 GdkPixbuf 使用的像素格式。

    如果您需要将cairo绘图转换为可以被GTK高效显示的格式,您可能需要使用图像表面和 gdk_memory_texture_new()