原文地址:Advanced GTK Techniques。
譯文將 Widget 理解為為“控件”戏蔑,Container 理解為“容器”蹋凝,Method 理解為“方法/函數(shù)”。
自定義容器
對于 GTK 而言总棵,(控件的)空間分配并不是一個十分困難的任務鳍寂。GTK 中的控件會根據(jù)從屬關系自下而上給出自身的偏好尺寸,包含它們的容器據(jù)此為子控件做出分配情龄,并計算出自己的偏好尺寸伐割,然后繼續(xù)向上層反饋。這一過程通過調(diào)用 gtk_widget_get_preferred_height()
和 gtk_widget_get_preferred_width()
即可實現(xiàn)刃唤;此外隔心,GTK 還提供了其它函數(shù)方便人們通過獲取的寬度確定高度,亦或通過高度確定寬度尚胞。知曉了各個控件的偏好尺寸后硬霍,容器便會自上而下開始分配工作,通過 gtk_widget_size_allocate()
為其子控件開辟空間笼裳。由此可知唯卖,如果開發(fā)者重寫了這個函數(shù)粱玲,便可實現(xiàn)容器對控件尺寸分配方法的控制。
上述過程與 GTK 2 的工作原理并不相符拜轨,有關 GTK 2 的講解可以查閱網(wǎng)絡上的其它資源抽减。
在大多數(shù)情形中——比如創(chuàng)建一個組合控件或者向已有的容器添加功能,你可以通過編寫一個容器的子類來實現(xiàn)自定義容器的自定義(例如編寫一個 GtkGrid
的子類)橄碾,這樣就不需要直接面對如何處理控件尺寸分配或者其它棘手的問題卵沉,而是通通交給它的父類容器解決。但是如果需要一個能夠以不同于現(xiàn)有 GTK 容器處理方式的部件時又該怎么辦法牲?這就需要我們創(chuàng)造一個新的容器類型史汗,并自行設計它的尺寸分配算法。
可惜的是拒垃,目前采用這種方法的實例往往過于復雜停撞,不利于教學,因此我們將會編寫一個幾乎沒什么用的容器悼瓮,它將所有的子控件放置在一個“表格”中戈毒,每個控件占用一個格子,頗有種 80 年代影視剪輯中分屏效果的風味横堡,就像 Heat of the Moment 一樣副硅。這個容器會查找整數(shù) n
使得:
式中 V
表示容器中可見的子控件數(shù)量。這個容器將會被分割成 n × n
的表格翅萤,從左向右恐疲、從上向下依次填充。
頭文件
我們將創(chuàng)建一個名為 PSquare
的容器套么,它是 GtkContainer
的子類培己。PSquare
的頭文件 psquare.h
在之前的教學中已有涉及,就不在此贅述胚泌。頭文件僅會輸出兩個函數(shù):p_square_get_type()
和 p_square_new()
省咨,代碼中的有趣之處在于重寫 GtkContainer
函數(shù)的部分。
類樣板
我們將簡單介紹文件 psquare.c
中涉及到 GObject
的知識點玷室。了解 G_DEFINE_TYPE
宏和 g_type_class_add_private()
的機制對我們而言有益無害零蓉,而且并不是每篇教程都會講這些。
// psquare/psquare.c
G_DEFINE_TYPE(PSquare, p_square, GTK_TYPE_CONTAINER);
G_DEFINE_TYPE
宏非常有用穷缤,它可以免除你很多的輸入工作敌蜂。它為 PSquare
創(chuàng)建了在頭文件中聲明過的 p_square_get_type()
函數(shù)、定義了名為 p_square_parent_class
的局部變量津肛;此外章喉,還聲明了 p_square_class_init()
和 p_square_init()
兩個局部函數(shù)。
// psquare/psquare.c
static void
p_square_class_init(PSquareClass *klass)
{
/* Override GtkWidget methods */
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
widget_class->get_preferred_width = p_square_get_preferred_width;
widget_class->get_preferred_height = p_square_get_preferred_height;
widget_class->size_allocate = p_square_size_allocate;
/* Override GtkContainer methods */
GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
container_class->child_type = p_square_child_type;
container_class->add = p_square_add;
container_class->remove = p_square_remove;
container_class->forall = p_square_forall;
/* Add private indirection member */
g_type_class_add_private(klass, sizeof(PSquarePrivate));
}
在函數(shù) p_square_class_init()
中引入 parent_class
變量是為了避免每次鏈接父類時都調(diào)用 g_type_class_peek_parent()
(常出現(xiàn)于 finalize
過程中)。你需要為自己類和實例編寫初始化函數(shù)秸脱。
類初始化函數(shù) p_square_class_init
重寫了多個父類的方法落包。如果你不熟悉這種 “GObject
式”的實現(xiàn)過程,不妨現(xiàn)在就了解一下摊唇。我們的測試程序不會用到類中的 forall
和 child_type
方法咐蝇,但是瀏覽 gtkcontainer.c
后可以看到二者在父類中均被設置為 NULL
——不折不扣的“虛”函數(shù),所以也需要重寫巷查。
為了找出父類的哪些方法需要被重寫有序,你不得不瀏覽
GtkContainer
的源代碼,從 API 文檔中并不能很好地做出分辨吮便。
類初始化函數(shù)做的另外一件事是注冊一個 PSquare
的私有成員笔呀,這通常是隱藏類中實現(xiàn)方法細節(jié)最有效的方式幢踏。代碼中的 g_type_class_add_private()
會定義私有成員的結(jié)構髓需,并通知 GObject
為類的每一個實例分配私有成員內(nèi)存。這樣一來房蝉,每個實例就都有了私有成員部分僚匆,而你只能通過編寫接口函數(shù)實現(xiàn)對它們的訪問。私有結(jié)構通過如下代碼定義:
// psquare/psquare.c
#define P_SQUARE_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), P_SQUARE_TYPE, PSquarePrivate))
typedef struct _PSquarePrivate PSquarePrivate;
struct _PSquarePrivate
{
GList *children;
};
或許將這些代碼專門放入類似于名為 psquare-private.h
的文件中是個更好的選擇搭幻。這樣咧擂,任何包含它的文件將可以訪問私有成員,類似于 C++ 中的友元數(shù)據(jù)檀蹋。如果你編寫的類體積巨大松申,這么做也有助于明晰結(jié)構。
P_SQUARE_PRIVATE()
宏可以返回 PSquare
的私有成員俯逾。編寫的例子中僅有一個私有成員數(shù)據(jù)贸桶,我們需要自己去維護它。
譯者注:在很多實際的 GTK 程序中桌肴,更為常見的私有成員實現(xiàn)方法為在 psquare/psquare.c 中輸入:
G_DEFINE_TYPE_WITH_PRIVATE(PSquare, p_square, GTK_TYPE_CONTAINER);
皇筛。這將直接聲明一個帶私有成員PSquarePrivate
的類,無需再在類初始化函數(shù)中引入g_type_class_add_private()
坠七。
通過這一宏定義類后水醋,在函數(shù)中調(diào)取私有成員可直接使用PSquarePrivate *priv = p_square_get_instance_private()
函數(shù),因此P_SQUARE_PRIVATE()
也不需要了彪置。
實例初始化函數(shù) p_square_init()
負責將公有和私有成員實例化:
// psquare/psquare.c
static void
p_square_init(PSquare *square)
{
gtk_widget_set_has_window(GTK_WIDGET(square), FALSE);
/* Initialize private members */
PSquarePrivate *priv = P_SQUARE_PRIVATE(square);
priv->children = NULL;
}
gtk_widget_set_has_window()
值為 FALSE
意味著 PSquare
不具有 GdkWindow
拄踪,因此我們不會自己完成繪制操作,而只是負責組織容器的子控件拳魁。正如代碼中展示的宫蛆,私有成員 children
初始化為 NULL
——一個空的 GList
。
還有一件約定俗成的事情是你需要知道的:編寫的控件通常通過 XXXX_new(p_square_new()
)函數(shù)產(chǎn)生,其返回值為一個新的類的實例耀盗,并強制轉(zhuǎn)換為 GtkWidget
格式:
// psquare/psquare.c
GtkWidget *
p_square_new()
{
return GTK_WIDGET(g_object_new(p_square_get_type(), NULL));
}
重寫 GtkContainer 方法
現(xiàn)在我們介紹如何重寫父類的方法想虎。GtkContainer
是一個非常基本的類叛拷,因此我們就從它入手舌厨。
child_type
方法用于確定哪些類型的子控件可以被裝入容器中,這一項我們只要返回 GTK_TYPE_WIDGET
就好了忿薇。GtkContainerClass
結(jié)構中的 forall
指針并未同一個 GtkContainer
方法對應裙椭,而是在程序運行過程中用于與 gtk_container_forall()
和 gtk_container_foreach()
對接。這兩個函數(shù)的前者意味著將對容器中包括“內(nèi)部”成員在內(nèi)的所有成員執(zhí)行操作(譯者注:“內(nèi)部”成員指并非由用戶添加的控件署浩,而是由容器創(chuàng)建揉燃,例如對話框中自動生成的按鈕),后者則會跳過“內(nèi)部”成員筋栋。forall
函數(shù)中會讀取一個是否包括“內(nèi)部”成員的標記炊汤,我們的 PSquare
沒有“內(nèi)部”成員,所以直接忽略它即可弊攘。
// psquare/psquare.c
static void
p_square_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data)
{
PSquarePrivate *priv = P_SQUARE_PRIVATE(container);
g_list_foreach(priv->children, (GFunc)callback, callback_data);
}
下一項工作是實現(xiàn) add
和 remove
方法抢腐。需要注意的是,你并非一定需要重寫這兩個方法襟交,僅在例如容器需要與父類不同的添加控件方法時才會這么做迈倍。GtkContainer
類中這兩個方法默認情況下不會做任何事,而是發(fā)出一個“方法未被實現(xiàn)”的警告捣域。
// psquare/psquare.c
static void
p_square_add(GtkContainer *container, GtkWidget *widget)
{
g_return_if_fail(container || P_IS_SQUARE(container));
g_return_if_fail(widget || GTK_IS_WIDGET(widget));
g_return_if_fail(gtk_widget_get_parent(widget) == NULL);
PSquarePrivate *priv = P_SQUARE_PRIVATE(container);
/* Add the child to our list of children.
* All the real work is done in gtk_widget_set_parent(). */
priv->children = g_list_append(priv->children, widget);
gtk_widget_set_parent(widget, GTK_WIDGET(container));
/* Queue redraw */
if(gtk_widget_get_visible(widget))
gtk_widget_queue_resize(GTK_WIDGET(container));
}
你可能會對 p_square_add
的簡短感到驚訝啼染,但正如注釋中所述,所有核心的工作都在 gtk_widget_set_parent()
中完成:添加引用計數(shù)焕梅、重繪控件迹鹅、觸發(fā)相應的信號,等等丘侠。這之后我們?nèi)孕枰孕欣L制容器徒欣。移除子控件的方法與此類似,一切核心工作都在 gtk_widget_unparent()
中完成蜗字。
// psquare/psquare.c
static void
p_square_remove(GtkContainer *container, GtkWidget *widget)
{
g_return_if_fail(container || P_IS_SQUARE(container));
g_return_if_fail(widget || GTK_IS_WIDGET(widget));
PSquarePrivate *priv = P_SQUARE_PRIVATE(container);
/* Remove the child from our list of children.
* Again, all the real work is done in gtk_widget_unparent(). */
GList *link = g_list_find(priv->children, widget);
if(link) {
gboolean was_visible = gtk_widget_get_visible(widget);
gtk_widget_unparent(widget);
priv->children = g_list_delete_link(priv->children, link);
/* Queue redraw */
if(was_visible)
gtk_widget_queue_resize(GTK_WIDGET(container));
}
}
這里有一點讓人欣慰:我們不需要編寫 p_square_destroy()
或者 p_square_finalize()
方法打肝,GtkContainer
會在容器析構時自動處理這些。
譯者注:一般而言挪捕,在實例創(chuàng)建過程中粗梭,
GtkWidget
通常被GtkContainer
包含,層層遞進级零,GTK 會自動處理它們的釋放過程断医,不需要用戶花費精力滞乙;而對于用戶在XxxxPrivate
中添加的其它私有成員,則可能需要手動管理內(nèi)存釋放的問題鉴嗤。
空間規(guī)劃
我們已經(jīng)完成了一切準備工作斩启,現(xiàn)在我們開始重點部分。我們需要確定容器中每添加一個格子時容器的寬度(或高度)需要增加多少醉锅。本示例中兔簇,格子的寬度與容器中最寬的子控件相等。格子的高度將采用相似的方式確定硬耍。此外垄琐,我們也需要考慮 GtkContainer
的 border_width
屬性帶來的影響。
// psquare/psquare.c
static void p_square_get_preferred_width(GtkWidget *widget, int *minimal, int *natural);
static void p_square_get_preferred_height(GtkWidget *widget, int *minimal, int *natural);
容器的類中有兩個返回自身尺寸的函數(shù):一個返回寬度经柴,一個返回高度狸窘。它們的參數(shù)中有兩個指向整數(shù)的指針,一個必須填寫尺寸的最小值坯认,另一個填寫默認值翻擒。
在本示例中,這兩個函數(shù)的運作方式近乎相同鹃操,所以我們將它們封裝到一個函數(shù)中—— get_size()
韭寸。這個函數(shù)有一個額外的 GtkOrientation
參數(shù)春哨,當我們需要輸入寬度時荆隘,將其設置為 GTK_ORIENTATION_HORIZONTAL
,需要輸入高度時則設置為 GTK_ORIENTATION_VERTICAL
赴背。
// psquare/psquare.c
static void get_size(PSquare *self, GtkOrientation direction, int *minimal, int *natural);
我們調(diào)用兩次 get_size()
函數(shù)椰拒,分別設置格子的寬度和高度。隨后凰荚,我們計算容器的尺寸燃观,也就是表格的行數(shù)和列數(shù),由于容器是一個正方形便瑟,所以用一個 n_groups
表示表格的邊長即可缆毁。如果 n_groups
為零,就直接返回到涂。
隨后脊框,我們遍歷各個子控件,通過 get_group_sizes()
獲取它們的尺寸践啄。容器的最小尺寸和默認尺寸都存儲在 GtkRequestedSize
結(jié)構中浇雹。一旦獲得了這些信息,我們就將它們累加屿讽,得到總大小昭灵。
// psquare/psquare.c
static void
get_size(PSquare *self, GtkOrientation direction, int *minimal, int *natural)
{
/* Start with the container's border width */
unsigned border_width =
gtk_container_get_border_width(GTK_CONTAINER(self));
*minimal = *natural = border_width * 2;
/* Find out how many children there are */
unsigned n_groups = get_n_columns_and_rows(self);
if(n_groups == 0)
return;
/* Find out how much space they want */
GtkRequestedSize *sizes = get_group_sizes(self, direction, n_groups);
/* Add the widths and pass that as the container's width */
unsigned count;
for(count = 0; count < n_groups; count++) {
*minimal += sizes[count].minimum_size;
*natural += sizes[count].natural_size;
}
g_free(sizes);
}
函數(shù) get_n_columns_and_rows()
如下所示。注意容器中可能會有不可見的子控件,PSquare
并不會為它們分配位置烂完,所以 n_groups
有可能在還有子控件時被設置為零试疙。
// psquare/psquare.c
unsigned
get_n_columns_and_rows(PSquare *self)
{
PSquarePrivate *priv = P_SQUARE_PRIVATE(self);
/* Count the visible children */
unsigned n_visible_children = 0;
g_list_foreach(priv->children, (GFunc)count_visible_children,
&n_visible_children);
if(n_visible_children == 0)
return 0;
/* Calculate the number of columns */
return (unsigned)ceil(sqrt((double)n_visible_children));
}
// psquare/psquare.c
/* Convenience function for counting the number of visible
* children, for use with g_list_foreach() */
static void
count_visible_children(GtkWidget *widget, unsigned *n_visible_children)
{
if(gtk_widget_get_visible(widget))
(*n_visible_children)++;
}
接下來我們講解 get_group_sizes()
,這是空間計算最為核心的工作抠蚣。
首先效斑,我們創(chuàng)建一個數(shù)組來存儲所有的控件組信息(即列的寬度或行的高度)。在獲取了所有子控件的偏好尺寸后柱徙,我們找出每組信息中的最大值缓屠。
// psquare/psquare.c
GtkRequestedSize *sizes = g_new0(GtkRequestedSize, n_groups);
隨后我們再次遍歷所有子控件,詢問它們的偏好尺寸护侮。如果設置了寬度模式敌完,我們獲取寬度,反之獲取高度羊初。這一過程中我們引入一個變量 group_num
滨溉,它在寬度模式中記錄當前子控件的行號,在高度模式中記錄列號长赞。
隨后我們獲取每組尺寸的最大值:如果子控件的尺寸大于所屬組的值晦攒,就把組值替換。完成這些工作后得哆,我們返回 sizes
數(shù)組脯颜。
// psquare/psquare.c
unsigned count = 0;
GList *iter;
for(iter = priv->children; iter; iter = g_list_next(iter)) {
if(!gtk_widget_get_visible(iter->data))
continue;
int child_minimal, child_natural;
unsigned group_num;
if(direction == GTK_ORIENTATION_HORIZONTAL) {
gtk_widget_get_preferred_width(iter->data,
&child_minimal, &child_natural);
group_num = count % n_groups;
} else {
gtk_widget_get_preferred_height(iter->data,
&child_minimal, &child_natural);
group_num = count / n_groups;
}
sizes[group_num].minimum_size =
MAX(child_minimal, sizes[group_num].minimum_size);
sizes[group_num].natural_size =
MAX(child_natural, sizes[group_num].natural_size);
count++;
}
空間分配
size_allocate
方法的執(zhí)行與上述過程相似。它接收一個 GtkAllocation
結(jié)構體贩据,其中包含了控件必須具有的尺寸大小信息栋操。
// psquare/psquare.c
static void p_square_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
我們首先需要將尺寸分配信息寫入控件:
// psquare/psquare.c
gtk_widget_set_allocation(widget, allocation);
下一步的工作與 size_request
方法十分相似。首先我們獲取行數(shù)和列數(shù):
// psquare/psquare.c
unsigned n_columns, n_rows;
n_columns = n_rows = get_n_columns_and_rows(P_SQUARE(widget));
if(n_columns == 0)
return;
n_columns
與 n_rows
事實上是相同的饱亮,但為了便于理解還是在代碼中區(qū)別對待矾芙。再一次地,如果容器中沒有可見的子控件近上,我們就直接返回剔宪。
現(xiàn)在我們需要將容器開辟的空間分配給它的子控件。我們的策略是在將額外的寬度平均分配到每一列壹无,將額外的高度平均分配到每一行葱绒。如果剩余空間過小,就反過來從每行或每列抽取相等的長度分配給新的控件格遭。首先我們計算出每列每行空間富余或缺少的長度哈街,并用兩個變量來表示: extra_width
和 extra_height
。它們的初始值為總寬度/高度拒迅,隨后將會減去我們需要的長度骚秦。首先需要減去的是容器的邊界寬度:
// psquare/psquare.c
unsigned border_width =
gtk_container_get_border_width(GTK_CONTAINER(widget));
int extra_width = allocation->width - 2 * border_width;
int extra_height = allocation->height - 2 * border_width;
隨后我們通過上面提到的 get_group_sizes()
函數(shù)獲得每列的寬度她倘。通過計算,我們?yōu)槊苛刑砑宇~外長度(這個值可能是負數(shù))作箍,得出每列的實際寬度硬梁。這些工作在函數(shù) distribute_extra_space()
中完成,我們將在稍后介紹它胞得。
完成列寬的工作后荧止,我們繼續(xù)計算每行的高度。二者過程近乎相同阶剑,除了用一個名為 get_group_sizes_for_sizes()
的函數(shù)替換 get_group_sizes()
跃巡,它能夠為已知寬度分配適宜的高度,反之亦然牧愁。我們在此不再給出這個函數(shù)的代碼素邪,它與 get_group_sizes()
的區(qū)別在于將 gtk_widget_get_preferred_height()
和 ..._width()
替換成了 gtk_widget_get_preferred_height_for_width()
和 ..._width_for_height()
。你可以在 psquare.c
文件中查看它的實現(xiàn)猪半。
// psquare/psquare.c
/* Follow the same procedure as in the size request to get
* the ideal sizes of each column */
GtkRequestedSize *widths = get_group_sizes(P_SQUARE(widget),
GTK_ORIENTATION_HORIZONTAL, n_columns);
/* Distribute the extra space per column (can be negative) */
unsigned count;
for(count = 0; count < n_columns; count++)
extra_width -= widths[count].minimum_size;
distribute_extra_space(P_SQUARE(widget), widths, extra_width, n_columns);
/* Follow the same procedure for height,
* now that we know the width */
GtkRequestedSize *heights = get_group_sizes_for_sizes(P_SQUARE(widget),
GTK_ORIENTATION_VERTICAL, widths, n_rows);
/* Distribute the extra space per row (can be negative) */
for(count = 0; count < n_rows; count++)
extra_height -= heights[count].minimum_size;
distribute_extra_space(P_SQUARE(widget), heights, extra_height, n_rows);
接下來的函數(shù)名為 distribute_extra_space()
兔朦,它將額外空間(可能為負數(shù))分配給每個組(即行或列)。GTK 已經(jīng)提供了一個便捷的函數(shù)磨确,可以用于賦予一組控件額外的空間(但必須為非負數(shù))沽甥,以使得每個控件都能獲取盡可能多的空間。當額外空間值非負時乏奥,我們可以直接調(diào)用這個函數(shù)摆舟。如果仍有空間剩余,或者第一個位置空間不足英融,我們將會把富余或短缺的空間均分給組內(nèi)每個成員盏檐。需要注意我們不能把子控件安放在一個空間小于零的格子中歇式,因此如果出現(xiàn)了這樣的情況驶悟,我們需要從其它行列中“挪用”一些像素,直到值達到零為止材失。
// psquare/psquare.c
static void
distribute_extra_space(PSquare *self, GtkRequestedSize *sizes,
int extra_space, unsigned n_groups)
{
if(extra_space > 0) {
extra_space = gtk_distribute_natural_allocation(extra_space,
n_groups, sizes);
}
unsigned count;
int extra_per_group = extra_space / (int)n_groups;
for(count = 0; count < n_groups; count++) {
sizes[count].minimum_size += extra_per_group;
/* If this results in a negative width, redistribute
* pixels from other nonzero-width columns to this one */
if(sizes[count].minimum_size < 0) {
unsigned count2;
for(count2 = (count + 1) % n_groups;
sizes[count].minimum_size < 0;
count2++, count2 %= n_groups)
{
if(count2 == count || sizes[count2].minimum_size < 0)
continue;
sizes[count2].minimum_size--;
sizes[count].minimum_size++;
}
}
}
}
回到本章核心——空間分配痕鳍。我們設立一個點 (x, y)
用于記錄下一個子控件放置位置的左上點座標。需要注意的是 GtkAllocation
結(jié)構中的 x
和 y
記錄的是整個屏幕的座標(原作者是這樣認為的)龙巨,而不是從容器的左上角開始計算笼呆,所以你需要做出一些調(diào)整。
// psquare/psquare.c
/* Start positioning the items at the container's origin,
* less the border width */
int x = allocation->x + border_width;
int y = allocation->y + border_width;
隨后我們再次遍歷可見的子控件旨别。我們會在棧中為每個子控件分配一個包含了座標 (x, y)
以及寬高信息的 GtkAllocation
結(jié)構體诗赌,然后通過 gtk_widget_size_allocate()
函數(shù)應用這些設置。接著秸弛,我們需要更新下一個子控件的座標铭若,在完成了一行的分配后還需下移一行并返回新行左上角座標洪碳。
// psquare/psquare.c
count = 0;
GList *iter;
for(iter = priv->children; iter; iter = g_list_next(iter)) {
if(!gtk_widget_get_visible(iter->data))
continue;
/* Give the child its allocation */
GtkAllocation child_allocation;
child_allocation.x = x;
child_allocation.y = y;
child_allocation.width = widths[count % n_columns].minimum_size;
child_allocation.height = heights[count / n_columns].minimum_size;
gtk_widget_size_allocate(iter->data, &child_allocation);
/* Advance the x coordinate */
x += child_allocation.width;
count++;
/* If we've moved to the next row, return the x coordinate
* to the left, and advance the y coordinate */
if(count % n_columns == 0) {
x = allocation->x + border_width;
y += child_allocation.height;
}
}
范例程序
我們將提供一個簡單的范例程序來展示 PSquare
容器的效果。你可以下載 test-psquare.c
和它的 Makefile
自行編譯叼屠。我們首先創(chuàng)建一個 toplevel
窗口瞳腌,并用 gtk_widget_set_size_request()
設定它的大小(不過在程序運行中你仍能夠擴大窗口)镜雨。在一開始只有少量控件時嫂侍,容器中的空間相對充足;隨著控件越來越多地擠進來荚坞,它們就不得不開始縮小尺寸以適應新的布局挑宠。
我們?yōu)榇翱谔砑恿艘粋€具有新增控件按鈕和移除控件按鈕工具欄,這樣可以手動添加控件(gtk_container_add()
)或者移除最近添加的一個控件(gtk_container_remove()
)颓影。
編譯并運行程序后痹栖,你就可以嘗試添加很多控件。你會注意到瞭空,一個 GtkEntry
會占用很多寬度揪阿,所以包含了 GtkEntry
的列往往會在空間緊張時將其它列占用,不過咆畏,即使分配了負的尺寸南捂,理論上你也并不會收到警告。
練習
修改
p_square_size_allocate()
旧找,使得它在分配空間時能夠根據(jù)每列占總寬度比例進行規(guī)劃溺健。比如,如果空間不足钮蛛,原先較寬的列將騰出較多的空間鞭缭,而較窄的列則騰出較少的空間。修改
PSquare
的子控件排列方法魏颓,使得其能從左上角沿順時針方向放置子控件渐行。為
PSquare
的子控件屬性添加實現(xiàn)方法。例如诵冒,fill-horizontal
和fill-vertical
屬性撑碴,它們用于確定是否要將容器為其分配的空間沿橫向或縱向全部占用。你也可以新建一個將二者封裝在一起的屬性來決定是否讓控件占用整個空間叹话。對于這個任務偷遗,你可以使用兩個對齊選項,也可以用一個GtkAnchorType
來實現(xiàn)驼壶。
文章許可協(xié)議:Attribution-NonCommercial-ShareAlike 3.0 Unported