GTK+ 2.0 教學-產生一個組合元件

介紹

你可能樂於創建這樣一種類型的元件,它僅僅是其它 GTK 元件的一個組合。這個型態的元件沒有產生就不能處理任何事但卻提供了便利的方法以重複使用。在標準發佈中的FileSelection和ColorSelection元件就是這種類型元件的範例。

我們將要在這一節創建一個井字遊戲元件,一個 3X3 的開關按鈕矩陣,當同一列、同一行或是對角線的所有三個按鈕都被按下時觸發一個信號。

 注意:井字遊戲的完整程式碼會在GTK+ 2.0 教學-範例程式說明。

選擇一個父類別

通常,一個組合元件的父類別是一個容納組合元件的所有元素的容器類別。例如,FileSelection元件的父類別就是對話框類別。因為我們的按鈕排列在一個表格中,看起來應該讓表格類別作為我們的父類別。但不幸的是,這樣無法工作。元件的創建分為兩個函式 – 一個用戶呼叫的 WIDGETNAME_new() 函式,另一個是 WIDGETNAME_init() 函式作基本的初始化元件的工作,它不使用傳遞給 _new() 函式的參數。子元件只呼叫父元件的 _init 函式。但是這個分工不能在表格中正常工作,因為創建表格時需要知道表格的行數和列數。除非我們想重新實現 gtk_table_new() 的大多數功能,我們最好避免從表格衍生元件。由於這個原因,代替表格,我們從縱向盒衍生元件,然後把表格入縱向盒。

標頭檔

每個GObject類別有一個標頭檔,該標頭檔用於宣告元件的物件、類別結構和公用函式。有兩個特性是值得指出的。為避免重複定義,我們把整個標頭檔包在:

#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */

並且讓 C++ 程式也能包含該標頭檔:

#include <glib.h>

G_BEGIN_DECLS
.
.
.
G_END_DECLS

在我們的標頭檔裡,和函式、結構一起宣告的還有三個標準巨集。 TICTACTOE(obj), TICTACTOE_CLASS(class)IS_TICTACTOE(obj),這三個巨集分別是將指標轉換為指向物件、類別的指標和檢測一個物件是否是一個井字遊戲元件。

_get_type() 函式

現在,我們繼續做我們的元件。WIDGETNAME_get_type() 函式是每個元件的核心函式。當第一次呼叫時,該函式告之 GTK 這個元件類別並得到一個能唯一識別該元件類別的 ID。之後的呼叫,只傳回這個ID。

GType
tictactoe_get_type (void)
{
  static GType ttt_type = 0;

  if (!ttt_type)
    {
      const GTypeInfo ttt_info =
      {
	sizeof (TictactoeClass),
	NULL, /* base_init */
	NULL, /* base_finalize */
	(GClassInitFunc) tictactoe_class_init,
	NULL, /* class_finalize */
	NULL, /* class_data */
	sizeof (Tictactoe),
	0,    /* n_preallocs */
	(GInstanceInitFunc) tictactoe_init,
      };

      ttt_type = g_type_register_static (GTK_TYPE_TABLE,
                                         "Tictactoe",
                                         &ttt_info,
                                         0);
    }

  return ttt_type;
}

GtkTypeInfo 結構定義如下:

struct _GTypeInfo
{
  /* interface types, classed types, instantiated types */
  guint16                class_size;
   
  GBaseInitFunc          base_init;
  GBaseFinalizeFunc      base_finalize;
   
  /* classed types, instantiated types */
  GClassInitFunc         class_init;
  GClassFinalizeFunc     class_finalize;
  gconstpointer          class_data;
   
  /* instantiated types */
  guint16                instance_size;
  guint16                n_preallocs;
  GInstanceInitFunc      instance_init;
   
  /* value handling */
  const GTypeValueTable *value_table;
};

這個結構的欄位不用加以說明就可以明瞭。我們在這裡忽略 arg_set_funcarg_get_func 以及value_table等欄位,一旦 GTK 正確的填充了該結構,它就知道了如何去創建一個特殊型別的物件。

_class_init() 函式

WIDGETNAME_class_init()函式初始化元件類別結構的欄位,並為類別設置任何信號。我們的井字遊戲元件是這樣的:

enum {
  TICTACTOE_SIGNAL,
  LAST_SIGNAL
};

static guint tictactoe_signals[LAST_SIGNAL] = { 0 };

static void
tictactoe_class_init (TictactoeClass *klass)
{
  tictactoe_signals[TICTACTOE_SIGNAL] =
    g_signal_new ("tictactoe",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (TictactoeClass, tictactoe),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

我們的元件只有一個tictactoe信號,當連成一行、一列或一個對角線時,該信號被觸發。並不是每個組合元件都需要信號,因此當你第一次閱讀這節時,可以跳過看下一節,因為這一節對初學者有點難。 函式:

guint g_signal_new( const gchar         *signal_name,
                    GType                itype,
                    GSignalFlags         signal_flags,
                    guint                class_offset,
                    GSignalAccumulator  *accumulator,
                    gpointer             accu_data,
                    GSignalCMarshaller  *c_marshaller,
                    GType                return_type,
                    guint                n_params,
                    ...);

創建一個新信號。參數是:

  • name:信號的名稱。
  • run_type:設定是在用戶處理函式之前還是之後執行預設處理函式。該值通常會是 GTK_RUN_FIRSTGTK_RUN_LAST,雖然也有其它值。
  • object_type:發出該信號的物件的ID。(也可能是子物件的ID。)
  • function_offset:類別結構指標相對於預設處理函式的偏移量。
  • marshaller:一個用於呼叫信號處理函式的函式。對於除了發出信號的物件和用戶資料外沒有其它的參數的信號處理函式,我們可以使用事先提供的 marshaller 函式 gtk_signal_default_marshaller
  • return_val:傳回值的型別。
  • nparams:信號處理函式的參數個數 (除了前面提到的”發出信號的物件”和”用戶資料”這兩個預設參數 )
  • ...:參數的資料型別。

當指定型別時,要用到下列的標準型別:

G_TYPE_INVALID
G_TYPE_NONE
G_TYPE_INTERFACE
G_TYPE_CHAR
G_TYPE_UCHAR
G_TYPE_BOOLEAN
G_TYPE_INT
G_TYPE_UINT
G_TYPE_LONG
G_TYPE_ULONG
G_TYPE_INT64
G_TYPE_UINT64
G_TYPE_ENUM
G_TYPE_FLAGS
G_TYPE_FLOAT
G_TYPE_DOUBLE
G_TYPE_STRING
G_TYPE_POINTER
G_TYPE_BOXED
G_TYPE_PARAM
G_TYPE_OBJECT

gtk_signal_new() 傳回一個能識別信號的唯一整數,我們把它存儲在 tictactoe_signals 陣列裡,並用列舉來做索引。(依照慣例,列舉成員是信號的名稱,且是大寫的,但在這裡會與 TICTACTOE() 巨集衝突,因此我們用 TICTACTOE_SIGNAL 代替。)

_init() 函式

每個元件類別也需要一個初始化物件結構的函式。通常,該函式有個相當有限的任務,就是設置結構成員為預設值。對於組合元件,這個函式還創建成分(component)元件。

static void
tictactoe_init (Tictactoe *ttt)
{
  gint i,j;

  gtk_table_resize (GTK_TABLE (ttt), 3, 3);
  gtk_table_set_homogeneous (GTK_TABLE (ttt), TRUE);

  for (i=0;i<3; i++)
    for (j=0;j<3; j++)
      {
	ttt->buttons[i][j] = gtk_toggle_button_new ();
	gtk_table_attach_defaults (GTK_TABLE (ttt), ttt->buttons[i][j], 
				   i, i+1, j, j+1);
	g_signal_connect (ttt->buttons[i][j], "toggled",
			  G_CALLBACK (tictactoe_toggle), ttt);
	gtk_widget_set_size_request (ttt->buttons[i][j], 20, 20);
	gtk_widget_show (ttt->buttons[i][j]);
      }
}

其餘的…

另外還有一個函式每個元件(除了基本的元件型別,如 Bin 不能實例化外)都需要 – 被用戶呼叫以創建一個該型別的物件的函式。這個呼叫通常是 WIDGETNAME_new()。在有些元件裡,該函式獲得幾個參數,依據這幾個參數做一些設置,我們的井字遊戲元件沒有這樣做。另外兩個函數是針對井字遊戲元件的。 tictactoe_clear() 是一個公用函式,它重設元件中的所有按鈕至upper的位置。注意 gtk_signal_handler_block_by_data() 函式用來防止按鈕切換信號處理函式被不必要地觸發。 tictactoe_toggle() 是當用戶點擊按鈕時呼叫的信號處理函式。它判斷雙態按鈕中是否出現了導致贏的組合,如果是,則發出 “tictactoe” 信號。

GtkWidget*
tictactoe_new (void)
{
  return GTK_WIDGET ( g_object_new (TICTACTOE_TYPE, NULL));
}

void	       
tictactoe_clear (Tictactoe *ttt)
{
  int i,j;

  for (i=0;i<3;i++)
    for (j=0;j<3;j++)
      {
	g_signal_handlers_block_matched (G_OBJECT (ttt->buttons[i][j]),
                                         G_SIGNAL_MATCH_DATA,
                                         0, 0, NULL, NULL, ttt);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
				     FALSE);
	g_signal_handlers_unblock_matched (G_OBJECT (ttt->buttons[i][j]),
                                           G_SIGNAL_MATCH_DATA,
                                           0, 0, NULL, NULL, ttt);
      }
}

static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
  int i,k;

  static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
			     { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
			     { 0, 1, 2 }, { 0, 1, 2 } };
  static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
			     { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
			     { 0, 1, 2 }, { 2, 1, 0 } };

  int success, found;

  for (k=0; k<8; k++)
    {
      success = TRUE;
      found = FALSE;

      for (i=0;i<3;i++)
	{
	  success = success && 
	    GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
	  found = found ||
	    ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
	}
      
      if (success && found)
	{
	  g_signal_emit (G_OBJECT (ttt), 
			 tictactoe_signals[TICTACTOE_SIGNAL], 0);
	  break;
	}
    }
}

最後,一個使用我們的井字遊戲元件的程示範例:

#include <gtk/gtk.h>
#include "tictactoe.h"

/* Invoked when a row, column or diagonal is completed */
void
win (GtkWidget *widget, gpointer data)
{
  g_print ("Yay!n");
  tictactoe_clear (TICTACTOE (widget));
}

int 
main (int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *ttt;
  
  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  
  gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
  
  g_signal_connect (window, "destroy",
                    G_CALLBACK (exit), NULL);
  
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  /* Create a new Tictactoe widget */
  ttt = tictactoe_new ();
  gtk_container_add (GTK_CONTAINER (window), ttt);
  gtk_widget_show (ttt);

  /* And attach to its "tictactoe" signal */
  g_signal_connect (ttt, "tictactoe",
                    G_CALLBACK (win), NULL);

  gtk_widget_show (window);
  
  gtk_main ();
  
  return 0;
}
<<< Previous 單元首頁 Next >>>
GTK+ 2.0 教學-元件的解析 Up GTK+ 2.0 教學-從草稿中產生元件

感謝你看到這裡,很快就可以離開了,但最好的獎勵行動就是按一下幫我分享或留言,感恩喔~

點我分享到Facebook

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *