从文件中加载内容

在本课程中,你将学习如何要求用户选择一个文件,加载文件的内容,然后将这些内容放入我们的文本查看器的文本区域。

../../../_images/opening_files.png

添加“打开”按钮

为了打开一个文件,你需要让用户选择它。你可以按照以下说明添加一个 按钮 到窗口的标题栏,该按钮将打开一个文件选择对话框。

更新 UI 定义

  1. 打开 text_viewer-window.ui 文件

  2. 找到 AdwHeaderBar 部件的 object 定义

  3. 添加一个 GtkButtonobject 定义作为标题栏的子项,使用 start 类型将其打包到窗口装饰的领先边缘

<object class="AdwHeaderBar" id="header_bar">
  <child type="start">
    <object class="GtkButton" id="open_button">
      <property name="label">Open</property>
      <property name="action-name">win.open</property>
    </object>
  </child>
  <child type="end">
    <object class="GtkMenuButton">
      <property name="primary">True</property>
      <property name="icon-name">open-menu-symbolic</property>
      <property name="tooltip-text" translatable="yes">Menu</property>
      <property name="menu-model">primary_menu</property>
    </object>
  </child>
  1. 该按钮具有 open_button 标识符,因此你可以在窗口模板中绑定它。

  2. 该按钮还具有一个设置为 win.openaction-name 属性;当用户按下按钮时,此操作将被激活。

在你的源代码中绑定模板

  1. 打开 text_viewer-window.c 文件

  2. open_button 部件添加到 TextViewerWindow 的实例结构中

struct _TextViewerWindow
{
  AdwApplicationWindow  parent_instance;

  /* Template widgets */
  AdwHeaderBar *header_bar;
  GtkTextView *main_text_view;
  GtkButton *open_button;
};
  1. TextViewerWindow 的类初始化中绑定 open_button 部件,text_viewer_window_class_init

static void
text_viewer_window_class_init (TextViewerWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class, "/com/example/TextViewer/text_viewer-window.ui");
  gtk_widget_class_bind_template_child (widget_class,
                                        TextViewerWindow,
                                        header_bar);
  gtk_widget_class_bind_template_child (widget_class,
                                        TextViewerWindow,
                                        main_text_view);
  gtk_widget_class_bind_template_child (widget_class,
                                        TextViewerWindow,
                                        open_button);
}

添加“打开”操作

open 操作 添加到 TextViewerWindow 的实例初始化中。

一旦你将 open 操作添加到窗口,你就可以将其作为 win.open 来引用

  1. 修改 TextViewerWindow 实例初始化函数 text_viewer_window_init 以创建一个 GSimpleAction 并将其添加到窗口

static void
text_viewer_window__open_file_dialog (GAction          *action,
                                      GVariant         *parameter,
                                      TextViewerWindow *self);

static void
text_viewer_window_init (TextViewerWindow *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  g_autoptr (GSimpleAction) open_action =
    g_simple_action_new ("open", NULL);
  g_signal_connect (open_action,
                    "activate",
                    G_CALLBACK (text_viewer_window__open_file_dialog),
                    self);
  g_action_map_add_action (G_ACTION_MAP (self),
                           G_ACTION (open_action));
}
  1. 打开 text_viewer-application.c 源代码文件并找到 TextViewerApplication 实例初始化函数 text_viewer_application_init

  2. Ctrl + O 作为 win.open 操作的加速快捷键添加

    static void
    text_viewer_application_init (TextViewerApplication *self)
    {
      // ...
    
      gtk_application_set_accels_for_action (GTK_APPLICATION (self),
                                             "win.open",
                                             (const char *[]) {
                                               "<Ctrl>o",
                                               NULL,
                                             });
    }
    

选择文件

现在你已经添加了操作,你必须定义在操作被激活时将调用的函数。

  1. text_viewer_window__open_file_dialog 函数内部,你创建一个 GtkFileDialog 对象,它将向用户呈现一个 文件选择对话框

static void
text_viewer_window__open_file_dialog (GAction          *action G_GNUC_UNUSED,
                                      GVariant         *parameter G_GNUC_UNUSED,
                                      TextViewerWindow *self)
{
  g_autoptr (GtkFileDialog) dialog = gtk_file_dialog_new ();

  gtk_file_dialog_open (dialog,
                        GTK_WINDOW (self),
                        NULL,
                        on_open_response,
                        self);
}
  1. on_open_response 函数处理用户在选择文件并关闭对话框或仅关闭对话框而不选择文件后的响应

static void
on_open_response (GObject      *source,
                  GAsyncResult *result,
                  gpointer      user_data)
{
  GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
  TextViewerWindow *self = user_data;

  g_autoptr (GFile) file =
    gtk_file_dialog_open_finish (dialog, result, NULL);

  // If the user selected a file, open it
  if (file != NULL)
    open_file (self, file);
}

读取文件内容

读取文件的内容可能需要任意时间,并阻塞应用程序的控制流。因此,建议你异步加载文件。这需要在 open_file 函数中启动“读取”操作

static void
open_file (TextViewerWindow *self,
           GFile            *file)
{
  g_file_load_contents_async (file,
                              NULL,
                              (GAsyncReadyCallback) open_file_complete,
                              self);
}

一旦异步操作完成,或者如果发生错误,将调用 open_file_complete 函数,并且你需要完成异步加载操作

static void
open_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    TextViewerWindow *self)
{
  GFile *file = G_FILE (source_object);

  g_autofree char *contents = NULL;
  gsize length = 0;

  g_autoptr (GError) error = NULL;

  // Complete the asynchronous operation; this function will either
  // give you the contents of the file as a byte array, or will
  // set the error argument
  g_file_load_contents_finish (file,
                               result,
                               &contents,
                               &length,
                               NULL,
                               &error);

  // In case of error, print a warning to the standard error output
  if (error != NULL)
    {
      g_printerr ("Unable to open “%s”: %s\n",
                  g_file_peek_path (file),
                  error->message);
      return;
    }
 }

在文本区域内显示内容

现在你已经有了文件的内容,你可以在 GtkTextView 部件中显示它们。

  1. 验证文件的内容是否使用 UTF-8 编码,因为 GTK 需要所有文本部件都使用 UTF-8 编码

static void
open_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    TextViewerWindow *self)
{
  GFile *file = G_FILE (source_object);

  g_autofree char *contents = NULL;
  gsize length = 0;

  g_autoptr (GError) error = NULL;

  // Complete the asynchronous operation; this function will either
  // give you the contents of the file as a byte array, or will
  // set the error argument
  g_file_load_contents_finish (file,
                               result,
                               &contents,
                               &length,
                               NULL,
                               &error);

  // In case of error, print a warning to the standard error output
  if (error != NULL)
    {
      g_printerr ("Unable to open the “%s”: %s\n",
                  g_file_peek_path (file),
                  error->message);
      return;
    }

  // Ensure that the file is encoded with UTF-8
  if (!g_utf8_validate (contents, length, NULL))
    {
      g_printerr ("Unable to load the contents of “%s”: "
                  "the file is not encoded with UTF-8\n",
                  g_file_peek_path (file));
      return;
    }
}
  1. 修改 open_file_complete 函数以检索 GtkTextView 部件用于存储文本的 GtkTextBuffer 实例,并设置其内容

static void
open_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    TextViewerWindow *self)
{
  GFile *file = G_FILE (source_object);

  g_autofree char *contents = NULL;
  gsize length = 0;

  g_autoptr (GError) error = NULL;

  // Complete the asynchronous operation; this function will either
  // give you the contents of the file as a byte array, or will
  // set the error argument
  g_file_load_contents_finish (file,
                               result,
                               &contents,
                               &length,
                               NULL,
                               &error);

  // In case of error, print a warning to the standard error output
  if (error != NULL)
    {
      g_printerr ("Unable to open the “%s”: %s\n",
                  g_file_peek_path (file),
                  error->message);
      return;
    }

  // Ensure that the file is encoded with UTF-8
  if (!g_utf8_validate (contents, length, NULL))
    {
      g_printerr ("Unable to load the contents of “%s”: "
                  "the file is not encoded with UTF-8\n",
                  g_file_peek_path (file));
      return;
    }

  // Retrieve the GtkTextBuffer instance that stores the
  // text displayed by the GtkTextView widget
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);

  // Set the text using the contents of the file
  gtk_text_buffer_set_text (buffer, contents, length);

  // Reposition the cursor so it's at the start of the text
  GtkTextIter start;
  gtk_text_buffer_get_start_iter (buffer, &start);
  gtk_text_buffer_place_cursor (buffer, &start);
}

更新窗口标题

由于应用程序现在正在显示特定文件的内容,你应该确保用户界面反映这种新状态。一种方法是用文件的名称更新窗口标题。

由于文件名使用操作系统提供的原始文件编码,我们需要查询文件的 显示名称

  1. 修改 open_file_complete 函数以查询“显示名称”文件属性

  2. 使用显示名称设置窗口标题

static void
open_file_complete (GObject          *source_object,
                    GAsyncResult     *result,
                    TextViewerWindow *self)
{
  GFile *file = G_FILE (source_object);

  g_autofree char *contents = NULL;
  gsize length = 0;

  g_autoptr (GError) error = NULL;

  // Complete the asynchronous operation; this function will either
  // give you the contents of the file as a byte array, or will
  // set the error argument
  g_file_load_contents_finish (file,
                               result,
                               &contents,
                               &length,
                               NULL,
                               &error);

  // Query the display name for the file
  g_autofree char *display_name = NULL;
  g_autoptr (GFileInfo) info =
    g_file_query_info (file,
                       "standard::display-name",
                       G_FILE_QUERY_INFO_NONE,
                       NULL,
                       NULL);
  if (info != NULL)
    {
      display_name =
        g_strdup (g_file_info_get_attribute_string (info, "standard::display-name"));
    }
  else
    {
      display_name = g_file_get_basename (file);
    }

  // In case of error, print a warning to the standard error output
  if (error != NULL)
    {
      g_printerr ("Unable to open “%s”: %s\n",
                  g_file_peek_path (file),
                  error->message);
      return;
    }

  // Ensure that the file is encoded with UTF-8
  if (!g_utf8_validate (contents, length, NULL))
    {
      g_printerr ("Unable to load the contents of “%s”: "
                  "the file is not encoded with UTF-8\n",
                  g_file_peek_path (file));
      return;
    }

  // Retrieve the GtkTextBuffer instance that stores the
  // text displayed by the GtkTextView widget
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);

  // Set the text using the contents of the file
  gtk_text_buffer_set_text (buffer, contents, length);

  // Reposition the cursor so it's at the start of the text
  GtkTextIter start;
  gtk_text_buffer_get_start_iter (buffer, &start);
  gtk_text_buffer_place_cursor (buffer, &start);

  // Set the title using the display name
  gtk_window_set_title (GTK_WINDOW (self), display_name);
}

将“打开”快捷键添加到键盘快捷键帮助

键盘快捷键 帮助对话框是 GNOME Builder 中 GNOME 应用程序模板的一部分。GTK 会自动处理其创建以及向用户呈现该操作。

  1. 在源代码目录中找到 help-overlay.ui 文件

  2. 找到 GtkShortcutsGroup 定义

  3. 在快捷键组中添加一个新的 GtkShortcutsShortcut 定义,用于 win.open 操作

<object class="GtkShortcutsGroup">
  <property name="title" translatable="yes" context="shortcut window">General</property>
  <child>
    <object class="GtkShortcutsShortcut">
      <property name="title" translatable="yes" context="shortcut window">Open</property>
      <property name="action-name">win.open</property>
    </object>
  </child>

现在你应该能够运行应用程序,按下 打开 按钮或 Ctrl + O,并在你的系统中选择一个文本文件。例如,你可以导航到文本查看器项目目录,并选择源代码中的 COPYING 文件

../../../_images/opening_files_main.png