[譯文] GTK 進階技術 —— 自定義容器(Container)

原文地址: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)在就了解一下摊唇。我們的測試程序不會用到類中的 forallchild_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) addremove 方法抢腐。需要注意的是,你并非一定需要重寫這兩個方法襟交,僅在例如容器需要與父類不同的添加控件方法時才會這么做迈倍。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)在我們開始重點部分。我們需要確定容器中每添加一個格子時容器的寬度(或高度)需要增加多少醉锅。本示例中兔簇,格子的寬度與容器中最寬的子控件相等。格子的高度將采用相似的方式確定硬耍。此外垄琐,我們也需要考慮 GtkContainerborder_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_columnsn_rows 事實上是相同的饱亮,但為了便于理解還是在代碼中區(qū)別對待矾芙。再一次地,如果容器中沒有可見的子控件近上,我們就直接返回剔宪。

現(xiàn)在我們需要將容器開辟的空間分配給它的子控件。我們的策略是在將額外的寬度平均分配到每一列壹无,將額外的高度平均分配到每一行葱绒。如果剩余空間過小,就反過來從每行或每列抽取相等的長度分配給新的控件格遭。首先我們計算出每列每行空間富余或缺少的長度哈街,并用兩個變量來表示: extra_widthextra_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é)構中的 xy 記錄的是整個屏幕的座標(原作者是這樣認為的)龙巨,而不是從容器的左上角開始計算笼呆,所以你需要做出一些調(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-horizontalfill-vertical 屬性撑碴,它們用于確定是否要將容器為其分配的空間沿橫向或縱向全部占用。你也可以新建一個將二者封裝在一起的屬性來決定是否讓控件占用整個空間叹话。對于這個任務偷遗,你可以使用兩個對齊選項,也可以用一個 GtkAnchorType 來實現(xiàn)驼壶。


文章許可協(xié)議:Attribution-NonCommercial-ShareAlike 3.0 Unported

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氏豌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子热凹,更是在濱河造成了極大的恐慌泵喘,老刑警劉巖瞭吃,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涣旨,居然都是意外死亡歪架,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門霹陡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來和蚪,“玉大人,你說我怎么就攤上這事烹棉≡芘” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵浆洗,是天一觀的道長催束。 經(jīng)常有香客問我,道長伏社,這世上最難降的妖魔是什么抠刺? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮摘昌,結(jié)果婚禮上速妖,老公的妹妹穿的比我還像新娘。我一直安慰自己聪黎,他們只是感情好罕容,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稿饰,像睡著了一般锦秒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喉镰,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天旅择,我揣著相機與錄音,去河邊找鬼梧喷。 笑死砌左,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的铺敌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屁擅,長吁一口氣:“原來是場噩夢啊……” “哼偿凭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起派歌,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弯囊,失蹤者是張志新(化名)和其女友劉穎痰哨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匾嘱,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡斤斧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霎烙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撬讽。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖悬垃,靈堂內(nèi)的尸體忽然破棺而出游昼,到底是詐尸還是另有隱情,我是刑警寧澤尝蠕,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布烘豌,位于F島的核電站,受9級特大地震影響看彼,放射性物質(zhì)發(fā)生泄漏廊佩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一靖榕、第九天 我趴在偏房一處隱蔽的房頂上張望罐寨。 院中可真熱鬧,春花似錦序矩、人聲如沸鸯绿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓶蝴。三九已至,卻和暖如春租幕,著一層夾襖步出監(jiān)牢的瞬間舷手,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工劲绪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留男窟,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓贾富,卻偏偏與公主長得像歉眷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颤枪,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容