GTK+ 2.0 Tutorial

<<< Previous

Writing Your Own Widgets

Next >>>


Создание сложного виджета

Введение

Один тип виджета который может вас заинтересовать, создаёт виджет на базе других виджетов GTK. Этот тип виджета не делает ничего, что не могло бы быть сделано, не создавая новые виджеты, но обеспечивает удобный способ упаковать элементы пользовательского интерфейса для многократного использования. Виджеты FileSelection и ColorSelection являются примерами такого типа виджетов.

Пример виджета который мы рассмотрим в этом разделе - Tictactoe widget, массив 3x3 кнопок переключателей который создаёт сигнал когда  задействованы три переключателя в ряд по горизонтали или вертикали.

Выбор родительского класса

Родительский класс для сложного виджета - типичный контейнерный класс, который содержит все элементы сложного виджета.  Например родительский класс виджета FileSelection - Диалоговый класс (Dialog class).  Так как наши кнопки будут упорядочены в таблице, могло бы показаться естественным сделать наш родительский класс классом Таблицы (Table class). Но к сожалению, этот вариант не работает. Создание виджета разделено на две функции - функция WIDGETNAME_new() пользовательских вызовов, и функция WIDGETNAME_init() которая выполняет основную работу инициализации виджета, который не зависит от аргументов переданных в функцию the _new(). Дочерние виджеты только вызывают функцию _init их родительского виджета. Но это разделение не работает для таблиц, когда необходимо перед созданием знать число колонок и строк.  Если мы не хотим дублировать большинство функциональных возможностей gtk_table_new() в нашем виджете Tictactoe, лучше всего избежать использование таблицы. Поэтому мы создаём таблицу из Vbox и прикрепляем её внутри VBox.

Заголовочный файл

Каждый класс виджетов имеет заголовочные файлы в которых объявляются объекты и структуры классов для виджета, наряду с общими функциями. Есть некоторые особенности о которых стоит упомянуть. Чтобы предотвратить двойные определения, мы заключаем весь заголовочный файл в:

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

А для поддержки C++ программ помещаем заголовочный файл в оболочку:

#ifdef __cplusplus extern "C" { #endif /* __cplusplus */ . . . #ifdef __cplusplus } #endif /* __cplusplus */

Наряду с функциями и структурами, мы объявляем три стандартных макроопределения в нашем заголовочном файле, TICTACTOE (obj), TICTACTOE_CLASS (klass), и IS_TICTACTOE (obj), которые помещают указатель в указатель на объект или структуру класса и проверяют если объект - виджет Tictactoe соответственно.

Вот полный заголовочный файл:

/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifndef __TICTACTOE_H__ #define __TICTACTOE_H__ #include <gdk/gdk.h> #include <gtk/gtkvbox.h> #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe) #define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass) #define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ()) typedef struct _Tictactoe Tictactoe; typedef struct _TictactoeClass TictactoeClass; struct _Tictactoe { GtkVBox vbox; GtkWidget *buttons[3][3]; }; struct _TictactoeClass { GtkVBoxClass parent_class; void (* tictactoe) (Tictactoe *ttt); }; GtkType tictactoe_get_type (void); GtkWidget* tictactoe_new (void); void tictactoe_clear (Tictactoe *ttt); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __TICTACTOE_H__ */

Функция _get_type()

Продолжаем создание нашего виджета. Основная функция для каждого виджета - WIDGETNAME_get_type(). Когда вызывается эта функция,  сначала она сообщает GTK о классе виджета, а потом идентифицирует класс виджета уникальным ID. В последующие вызовы она просто  возвращает ID.

GtkType tictactoe_get_type () { static guint ttt_type = 0; if (!ttt_type) { GtkTypeInfo ttt_info = { "Tictactoe", sizeof (Tictactoe), sizeof (TictactoeClass), (GtkClassInitFunc) tictactoe_class_init, (GtkObjectInitFunc) tictactoe_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info); } return ttt_type; }

Структура GtkTypeInfo имеет следующее определение:

struct _GtkTypeInfo { gchar *type_name; guint object_size; guint class_size; GtkClassInitFunc class_init_func; GtkObjectInitFunc object_init_func; GtkArgSetFunc arg_set_func; GtkArgGetFunc arg_get_func; };

Значение полей этой структуры достаточно очевидно. Мы будем игнорировать arg_set_func и arg_get_func поля: они имеют важную, но пока мало используемую роль, позволяющую устанавливать варианты виджета интерпретируемых языков. Как только GTK получает корректно заполненную копию этой структуры, можно создавать объекты виджета специфического типа.

Функция _class_init()

Функция WIDGETNAME_class_init() инициализирует поля структуры класса виджета и настраивает любые сигналы для класса. Для нашего виджета Tictactoe это выглядит так:

enum { TICTACTOE_SIGNAL, LAST_SIGNAL }; static gint tictactoe_signals[LAST_SIGNAL] = { 0 }; static void tictactoe_class_init (TictactoeClass *class) { GtkObjectClass *object_class; object_class = (GtkObjectClass*) class; tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe), gtk_signal_default_marshaller, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL); class->tictactoe = NULL; }

Наш виджет имеет только один сигнал, сигнал tictactoe, который производится когда ряд, колонка, или диагональ полностью заполнены.  Не каждый сложный виджет нуждается в сигналах. Если вы новичок в программировании, вы можете перейти к следующему разделу, поскольку далее будет более сложный материал.

Функция:

gint gtk_signal_new( const gchar *name, GtkSignalRunType run_type, GtkType object_type, gint function_offset, GtkSignalMarshaller marshaller, GtkType return_val, guint nparams, ...);

Создаёт новый сигнал. Значения параметров:

Определяя типы, используется перечисление GtkType:

typedef enum { GTK_TYPE_INVALID, GTK_TYPE_NONE, GTK_TYPE_CHAR, GTK_TYPE_BOOL, GTK_TYPE_INT, GTK_TYPE_UINT, GTK_TYPE_LONG, GTK_TYPE_ULONG, GTK_TYPE_FLOAT, GTK_TYPE_DOUBLE, GTK_TYPE_STRING, GTK_TYPE_ENUM, GTK_TYPE_FLAGS, GTK_TYPE_BOXED, GTK_TYPE_FOREIGN, GTK_TYPE_CALLBACK, GTK_TYPE_ARGS, GTK_TYPE_POINTER, /* было бы хорошо, если следующие два могли бы быть удалены в конечном счете */ GTK_TYPE_SIGNAL, GTK_TYPE_C_CALLBACK, GTK_TYPE_OBJECT } GtkFundamentalType;

gtk_signal_new()  возвращает уникальный целочисленный идентификатор для сигнала, который мы храним в массиве tictactoe_signals, который мы индексируем используя перечисления.

После создания нашего сигнала, нужно чтобы GTK ассоциировал наш сигнал с Tictactoe class. Это делается вызовом функции gtk_object_class_add_signals(). Устанавливая указатель, который указывает на обработчик значения по умолчанию для сигнала "tictactoe" в NULL, указываем, что нет никакого действия по умолчанию.

Функция _init()

Каждый класс виджета также нуждается в функции, чтобы инициализировать структуру объекта.  Обычно, эта функция имеет справедливо ограниченную роль установки полей структуры к значениям по умолчанию. Для сложных виджетов, однако, эта функция также создает составные виджеты.

static void tictactoe_init (Tictactoe *ttt) { GtkWidget *table; gint i,j; table = gtk_table_new (3, 3, TRUE); gtk_container_add (GTK_CONTAINER(ttt), table); gtk_widget_show (table); 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(table), ttt->buttons[i][j], i, i+1, j, j+1); gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled", GTK_SIGNAL_FUNC (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, эта функция берет параметры, и делает некоторые установки, основанные на параметрах. Другие две функции являются специальными для виджета Tictactoe.

tictactoe_clear()  является общей функцией, которая сбрасывает все кнопки в виджете в позицию выключено (up). Обратите внимание на  использование gtk_signal_handler_block_by_data(), чтобы оградить наш обработчик сигнала для кнопок переключателей от излишних вызовов.

tictactoe_toggle()  является обработчиком сигнала, который вызывается, когда пользователь нажимает на кнопку. Выясняется наличие необходимой комбинации нажатых кнопок, если комбинация существует, производится сигнал "tictactoe".

GtkWidget* tictactoe_new () { return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ())); } void tictactoe_clear (Tictactoe *ttt) { int i,j; for (i=0;i<3;i++) for (j=0;j<3;j++) { gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]), FALSE); gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), 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) { gtk_signal_emit (GTK_OBJECT (ttt), tictactoe_signals[TICTACTOE_SIGNAL]); break; } } }

И наконец, пример программы, использующей наш виджет Tictactoe:

#include <gtk/gtk.h> #include "tictactoe.h" /* Вызывается когда строка, столбец, или диагональ заполнены */ 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"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); /* Создаём новый Tictactoe виджет */ ttt = tictactoe_new (); gtk_container_add (GTK_CONTAINER (window), ttt); gtk_widget_show (ttt); /* И присоединяем "tictactoe" сигнал */ gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe", GTK_SIGNAL_FUNC (win), NULL); gtk_widget_show (window); gtk_main (); return 0; }

<<< Previous

Home

Next >>>

The Anatomy Of A Widget

Up

Creating a widget from scratch