ZetCode

GTK+ 布局管理

最后修改于 2023 年 10 月 18 日

在本章中,我们将展示如何在窗口或对话框中布局我们的控件。

当我们设计应用程序的 UI 时,我们决定使用哪些控件以及如何组织这些控件。为了组织我们的控件,我们使用称为布局容器的特殊不可见控件。在本章中,我们提到了 GtkAlignmentGtkFixedGtkVBoxGtkTable

GtkFixed

GtkFixed 容器将子控件放置在固定位置并具有固定大小。此容器不执行自动布局管理。因此,它不适用于翻译、字体更改或主题。在大多数应用程序中,我们不使用 GtkFixed 容器。可能有一些可以使用该容器的特殊区域(例如,定位图表或图像)。

fixed.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {
    
  GtkWidget *window;
  GtkWidget *fixed;

  GtkWidget *btn1;
  GtkWidget *btn2;
  GtkWidget *btn3;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkFixed");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  btn1 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn1, 150, 50);
  gtk_widget_set_size_request(btn1, 80, 30);

  btn2 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn2, 15, 15);
  gtk_widget_set_size_request(btn2, 80, 30);

  btn3 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn3, 100, 100);
  gtk_widget_set_size_request(btn3, 80, 30);

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

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的示例中,我们创建了三个按钮并将它们放置在固定坐标处。当我们调整应用程序窗口的大小时,按钮会保持其大小和位置。

fixed = gtk_fixed_new();

get_fixed_new 函数创建一个 GtkFixed 容器。

gtk_fixed_put(GTK_FIXED(fixed), btn1, 150, 50);

第一个按钮使用 gtk_fixed_put 函数放置在坐标 x=150 和 y=50 处。

gtk_widget_set_size_request(btn1, 80, 30);

gtk_widget_set_size_request 为控件设置最小大小。这是控件在运行良好并正确绘制自身时可以接受的最小尺寸。

GtkFixed container
图:GtkFixed 容器

GtkAlignment

GtkAlignment 控制控件的对齐方式。此外,它可以管理其缩放。

bottomleft.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {
    
  GtkWidget *window;
  GtkWidget *align;

  GtkWidget *lbl;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkAlignment");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  align = gtk_alignment_new(0, 1, 0, 0);
  lbl = gtk_label_new("bottom-left");
  
  gtk_container_add(GTK_CONTAINER(align), lbl);
  gtk_container_add(GTK_CONTAINER(window), align);

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

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,标签位于窗口的左下角。

align = gtk_alignment_new(0, 1, 0, 0);

gtk_alignment_new 函数创建 GtkAlignment 容器。参数取值范围为 0 到 1。第一个参数是水平对齐,其中 0 是左对齐,1 是右对齐。第二个参数是垂直对齐,其中 0 是顶部对齐,1 是底部对齐。第三个参数是水平缩放,它是子控件水平扩展以填充未使用的空间的量。值为 0 表示子控件不应扩展。最后一个参数是垂直缩放。

lbl = gtk_label_new("bottom-left");

使用 gtk_label_new 函数创建标签控件。

gtk_container_add(GTK_CONTAINER(align), lbl);

标签添加到 GtkAlignment 容器中。

gtk_container_add(GTK_CONTAINER(window), align);

最后,对齐容器放置在窗口中。

GtkAlignment
图:GtkAlignment

GtkVBox

GtkVBox 是一个垂直框容器。它将子控件放置在单个列中。GtkHBox 是一个非常类似的容器;它将子控件放置在单个行中。

vbox.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *settings;
  GtkWidget *accounts;
  GtkWidget *loans;
  GtkWidget *cash;
  GtkWidget *debts;

  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, 250);
  gtk_window_set_title(GTK_WINDOW(window), "GtkVBox");
  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  vbox = gtk_vbox_new(TRUE, 1);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  settings = gtk_button_new_with_label("Settings");
  accounts = gtk_button_new_with_label("Accounts");
  loans = gtk_button_new_with_label("Loans");
  cash = gtk_button_new_with_label("Cash");
  debts = gtk_button_new_with_label("Debts");

  gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), accounts, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), loans, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), cash, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), debts, TRUE, TRUE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

此示例将五个按钮打包到一列中。如果我们调整应用程序窗口的大小,子控件也会调整大小。

vbox = gtk_vbox_new(TRUE, 1);

gtk_vbox_new 函数创建一个 GtkVBox 容器。我们将 homogeneous 参数设置为 TRUE。这意味着我们所有的按钮都将具有相同的大小。控件之间的间距设置为 1 像素。

gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);

gtk_box_pack_start 函数将一个控件添加到框中。前两个参数是框容器和子控件。接下来的三个参数是 expandfillpadding。请注意,如果 expand 参数设置为 FALSE,则填充参数无效。同样,如果我们使用 homogeneous 参数设置为 TRUE 创建了容器,则 expand 参数无效。在我们的例子中,当窗口扩大并且控件填充额外区域时,会为“设置”按钮提供额外的空间。

GtkVBox container
图:GtkVBox 容器

GtkTable

GtkTable 控件将控件排列在行和列中。

table.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;
  GtkWidget *button;

  gchar *values[16] = { "7", "8", "9", "/", 
     "4", "5", "6", "*",
     "1", "2", "3", "-",
     "0", ".", "=", "+"
  };

  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, 180);
  gtk_window_set_title(GTK_WINDOW(window), "GtkTable");

  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  table = gtk_table_new(4, 4, TRUE);
  gtk_table_set_row_spacings(GTK_TABLE(table), 2);
  gtk_table_set_col_spacings(GTK_TABLE(table), 2);

  int i = 0;
  int j = 0;
  int pos = 0;

  for (i=0; i < 4; i++) {
    for (j=0; j < 4; j++) {
      button = gtk_button_new_with_label(values[pos]);
      gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1);
      pos++;
    }
  }

  gtk_container_add(GTK_CONTAINER(window), table);

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

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在此示例中,我们创建了一组我们在计算器中看到的按钮。

table = gtk_table_new(4, 4, TRUE);

我们创建一个新的 GtkTable 控件,其中包含 4 行和 4 列。当我们将 TRUE 传递给第三个参数时,所有表格单元格都会调整为包含最大控件的单元格的大小。

gtk_table_set_row_spacings(GTK_TABLE(table), 2);
gtk_table_set_col_spacings(GTK_TABLE(table), 2);

我们在行和列之间设置一些空间。

for (i=0; i < 4; i++) {
  for (j=0; j < 4; j++) {
    button = gtk_button_new_with_label(values[pos]);
    gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1 );
    pos++;
  }
}

此代码创建 16 个按钮并将它们放置到容器中。gtk_table_attach_defaults 将子控件添加到具有相同填充和扩展选项的表格容器中。

GtkTable
图:GtkTable

角按钮

下一个示例将两个按钮放置在窗口的右下角。

cornerbuttons.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *okBtn;
  GtkWidget *clsBtn;

  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *halign;
  GtkWidget *valign;

  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), 350, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Corner buttons");
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  vbox = gtk_vbox_new(FALSE, 5);

  valign = gtk_alignment_new(0, 1, 0, 0);
  gtk_container_add(GTK_CONTAINER(vbox), valign);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  hbox = gtk_hbox_new(TRUE, 3);

  okBtn = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(okBtn, 70, 30);
  gtk_container_add(GTK_CONTAINER(hbox), okBtn);
  clsBtn = gtk_button_new_with_label("Close");
  gtk_container_add(GTK_CONTAINER(hbox), clsBtn);

  halign = gtk_alignment_new(1, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), hbox);

  gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,我们使用一个水平框、一个垂直框和两个对齐容器。

valign = gtk_alignment_new(0, 1, 0, 0);

此对齐容器将其子控件放置在底部。

gtk_container_add(GTK_CONTAINER(vbox), valign);

在这里,我们将对齐控件放入垂直框中。

hbox = gtk_hbox_new(TRUE, 3);

okBtn = gtk_button_new_with_label("OK");
gtk_widget_set_size_request(okBtn, 70, 30);
gtk_container_add(GTK_CONTAINER(hbox), okBtn);
clsBtn = gtk_button_new_with_label("Close");
gtk_container_add(GTK_CONTAINER(hbox), clsBtn);

我们创建一个水平框并在其中放置两个按钮。gtk_widget_set_size_request 设置控件的最小尺寸。由于我们将 GtkHBoxhomogeneous 参数设置为 TRUE,因此另一个按钮也会调整到新尺寸。

halign = gtk_alignment_new(1, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(halign), hbox);

gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);

这会创建一个对齐容器,该容器将其子控件放置在右侧。我们将水平框添加到对齐容器中,并将对齐容器打包到垂直框中。对齐容器只能接受一个子控件;因此,我们还必须使用框。

Corner buttons
图:角按钮

Windows

接下来,我们创建一个更高级的示例。我们展示一个可以在 JDeveloper 中找到的窗口。

Windows dialog in JDeveloper
图:JDeveloper 中的 Windows 对话框

该对话框显示所有已打开的窗口,或者更确切地说,是 JDeveloper 应用程序中的选项卡。

windows.c
#include <gtk/gtk.h>

int main(int argc, char *argv[]) {
    
  GtkWidget *window;
  GtkWidget *table;
  GtkWidget *title;
  GtkWidget *wins;
  
  GtkWidget *halign;
  GtkWidget *halign2;
  GtkWidget *valign;

  GtkWidget *actBtn;
  GtkWidget *clsBtn;
  GtkWidget *hlpBtn;
  GtkWidget *okBtn;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_widget_set_size_request (window, 300, 250);
  
  gtk_window_set_title(GTK_WINDOW(window), "Windows");

  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  table = gtk_table_new(6, 4, FALSE);
  gtk_table_set_col_spacings(GTK_TABLE(table), 3);
  gtk_table_set_row_spacing(GTK_TABLE(table), 0, 3);

  title = gtk_label_new("Windows");
  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), title);
  gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
      GTK_FILL, GTK_FILL, 0, 0);

  wins = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
  gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3, 
      GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);

  actBtn = gtk_button_new_with_label("Activate");
  gtk_widget_set_size_request(actBtn, 50, 30);
  gtk_table_attach(GTK_TABLE(table), actBtn, 3, 4, 1, 2, 
      GTK_FILL, GTK_SHRINK, 1, 1);

  valign = gtk_alignment_new(0, 0, 0, 0);
  clsBtn = gtk_button_new_with_label("Close");
 
  gtk_widget_set_size_request(clsBtn, 70, 30);
  gtk_container_add(GTK_CONTAINER(valign), clsBtn);
  gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
  gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
      GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);

  halign2 = gtk_alignment_new(0, 1, 0, 0);
  hlpBtn = gtk_button_new_with_label("Help");
  gtk_container_add(GTK_CONTAINER(halign2), hlpBtn);
  gtk_widget_set_size_request(hlpBtn, 70, 30);
  gtk_table_set_row_spacing(GTK_TABLE(table), 3, 5);
  gtk_table_attach(GTK_TABLE(table), halign2, 0, 1, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  okBtn = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(okBtn, 70, 30);
  gtk_table_attach(GTK_TABLE(table), okBtn, 3, 4, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  gtk_container_add(GTK_CONTAINER(window), table);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

该示例使用一个表格容器和三个对齐容器。

table = gtk_table_new(6, 4, FALSE);

创建一个 GtkTable 容器。它有六行和四列。

gtk_table_set_col_spacings(GTK_TABLE(table), 3);

gtk_table_set_col_spacings 将表格中每一列之间的间距设置为 3。

gtk_table_set_row_spacing(GTK_TABLE(table), 0, 3);

gtk_table_row_spacing 设置第一行和第二行之间的间距。

title = gtk_label_new("Windows");
halign = gtk_alignment_new(0, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(halign), title);
gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
    GTK_FILL, GTK_FILL, 0, 0);

此代码创建一个左对齐的标签。标签放置在 GtkTable 容器的第一行和第一列中。

wins = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3, 
    GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);

GtkText 控件跨越两行和两列。我们使用 gtk_text_view_set_editable 方法使该控件不可编辑,并使用 gtk_text_view_set_cursor_visible 方法隐藏其光标。

valign = gtk_alignment_new(0, 0, 0, 0);
clsBtn = gtk_button_new_with_label("Close");

gtk_widget_set_size_request(clsBtn, 70, 30);
gtk_container_add(GTK_CONTAINER(valign), clsBtn);
gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
    GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);

我们将“关闭”按钮放置在文本视图控件旁边,位于第四列中。我们将按钮添加到对齐控件中,以便我们可以将其与顶部对齐。

halign2 = gtk_alignment_new(0, 1, 0, 0);
hlpBtn = gtk_button_new_with_label("Help");
gtk_container_add(GTK_CONTAINER(halign2), hlpBtn);
gtk_widget_set_size_request(hlpBtn, 70, 30);
gtk_table_set_row_spacing(GTK_TABLE(table), 3, 5);
gtk_table_attach(GTK_TABLE(table), halign2, 0, 1, 4, 5, 
    GTK_FILL, GTK_FILL, 0, 0);

“帮助”按钮左对齐。它位于文本控件下方。我们在文本控件和按钮之间留出一些空间。

okBtn = gtk_button_new_with_label("OK");
gtk_widget_set_size_request(okBtn, 70, 30);
gtk_table_attach(GTK_TABLE(table), okBtn, 3, 4, 4, 5, 
    GTK_FILL, GTK_FILL, 0, 0);

“确定”按钮位于第二列中,位于“激活”和“关闭”按钮下方。

Windows
图:Windows

本章专门介绍布局管理。