| |
GTK+ 2.0 Tutorial |
||
---|---|---|
Writing Your Own Widgets |
Один тип виджета который может вас заинтересовать, создаёт виджет на базе других виджетов 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__ */ |
Продолжаем создание нашего виджета. Основная функция для каждого виджета - 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 получает корректно заполненную копию этой структуры, можно создавать объекты виджета специфического типа.
Функция 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, ...); |
Создаёт новый сигнал. Значения параметров:
run_type - выполняется ли по умолчанию, после или перед пользовательским обработчиком. Как правило это будет GTK_RUN_FIRST, или GTK_RUN_LAST, но могут быть и другие варианты.
object_type - ID из объекта, к которому этот сигнал обращается. (Это также применимо к объектам потомкам.)
function_offset - Смещение в пределах структуры класса указателя на обработчик значения по умолчанию.
Marshaller - Функция, которая используется, чтобы вызвать обработчик сигнала. Для обработчиков сигнала, которые не имеют никаких параметров кроме объекта, который произвёл сигнал и пользовательские данные, мы можем использовать pre-supplied marshaller функцию gtk_signal_default_marshaller.
Nparams - Число параметров обработчика сигнала (кроме двух упомянутых выше значений по умолчанию)
Определяя типы, используется перечисление GtkType:
gtk_signal_new() возвращает уникальный целочисленный идентификатор для сигнала, который мы храним в массиве tictactoe_signals, который мы индексируем используя перечисления.
После создания нашего сигнала, нужно чтобы GTK ассоциировал наш сигнал с Tictactoe class. Это делается вызовом функции gtk_object_class_add_signals(). Устанавливая указатель, который указывает на обработчик значения по умолчанию для сигнала "tictactoe" в NULL, указываем, что нет никакого действия по умолчанию.
Каждый класс виджета также нуждается в функции, чтобы инициализировать структуру объекта. Обычно, эта функция имеет справедливо ограниченную роль установки полей структуры к значениям по умолчанию. Для сложных виджетов, однако, эта функция также создает составные виджеты.
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; } |
The Anatomy Of A Widget |
Creating a widget from scratch |
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |