Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 59 (всего у книги 67 страниц)
События, сигналы и обратные вызовы
У всех библиотек GUI есть нечто общее. Должен существовать некий механизм для выполнения программного кода в ответ на действие пользователя. Программа, выполняющаяся в режиме командной строки, может позволить себе останов выполнения в ожидании ввода и затем применить нечто вроде оператора выбора для выполнения разных ветвей программы в зависимости от введенных данных. Такой подход нецелесообразен в случае приложения GUI, поскольку оно должно непрерывно реагировать на ввод пользователя, например, ему приходится постоянно обновлять области окна.
У современных оконных систем есть система событий и приемники событий, которым адресована эта задача. Идея заключается в том, что каждый пользовательский ввод обычно с помощью мыши или клавиатуры инициирует событие. Нажатие на клавиатуре, например, вызовет "событие клавиатуры". Затем пишется программный код, который ждет приема такого события и выполняется в случае его возникновения.
Как вы уже видели, эти события генерирует система X Window System, но они мало помогут вам как программисту GTK+, т.к. они очень низкоуровневые. Когда производится щелчок кнопкой мыши, X порождает событие, содержащее координаты указателя мыши, а вам нужно знать, когда пользователь активизирует виджет.
У GTK+ есть собственная система событий и приемников событий, называемых сигналами и обратными вызовами. Их очень легко применять, поскольку для установки обработчика сигнала можно использовать очень полезное свойство языка С, указатель на функцию.
Сначала несколько определений. Сигнал GTK+ порождается объектом типа GtkObject
, когда происходит нечто, например, ввод пользователя. Функция, связанная с сигналом и, следовательно, вызываемая при любом порождении сигнала, называется функцией обратного вызова.
Примечание
Имейте в виду, что сигнал GTK+ – это нечто иное, чем сигнал UNIX, обсуждавшийся в главе 11.
Как программист, использующий GTK+, вы должны заботиться только о написании и связывании функций обратного вызова, поскольку код порождения сигнала – это внутренний программный код определенного виджета.
Прототип или заголовок функции обратного вызова обычно похож на следующий:
void a_callback_function(GtkWidget *widget, gpointer user_data);
Вы передаете два параметра: первый – указатель на виджет, породивший сигнал, второй – произвольный указатель, который вы выбираете самостоятельно, когда связываете обратный вызов. Вы можете использовать этот указатель для любых целей.
Связать функцию обратного вызова тоже очень просто. Вы вызываете функцию g_signal_connect
и передаете ей виджет, имя сигнала в виде строки, указатель на функцию обратного вызова и ваш произвольный указатель:
gulong g_signal_connect(gpointer *object, const gchar *name,
GCallback func, gpointer user_data);
Следует отметить, что для связывания функций обратного вызова нет ограничений. Вы можете иметь много сигналов, связанных с одной и той же функцией обратного вызова, и много функций обратного вызова, связанных с единственным сигналом.
В документации по API GTK+ можно найти подробное описание сигналов, порождаемых каждым виджетом.
Примечание
До появления GTK+ 2 для связывания функций обратного вызова применялась функция
gtk_signal_connect
. Она была заменена функциейg_signal_connect
и не должна применяться во вновь разрабатываемом программном коде.
Вы опробуете функцию g_signal_connect
в упражнении 16.2.
Упражнение 16.2. Функция обратного вызова
В программе gtk2.c вставьте в свое окно кнопку и свяжите сигнал clicked
(щелчок мышью по кнопке) с вашей функцией обратного вызова для вывода короткого сообщения:
#include
#include
static int count = 0;
void button_clicked(GtkWidget *button, gpointer data) {
printf(«%s pressed %d time(s) n», (char *)data, ++count);
}
int main(int argc, char* argv[]) {
GtkWidget *window;
GtkWidget *button;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
button = gtk_button_new_with_label(«Hello World!»);
gtk_container_add(GTK_CONTAINER(window), button);
g_signal_connect(GTK_OBJECT(button), «clicked»,
GTK_SIGNAL_FUNC(button_clicked), «Button 1»);
gtk_widget_show(button);
gtk_widget_show(window);
gtk_main();
return 0;
}
Введите исходный текст программы и сохраните его в файле с именем gtk2.c. Откомпилируйте и скомпонуйте программу аналогично программе gtk1.с из предыдущего упражнения. Запустив ее, вы получите окно с кнопкой. При каждом щелчке кнопки мышью будет выводиться короткое сообщение (рис. 16.5).
Рис. 16.5
Как это работает
Вы добавили два новых элемента в программу gtk2.c: виджет GtkButton
и функцию обратного вызова. GtkButton
– это виджет простой кнопки, которая может содержать текст, в нашем случае «Hello World», и порождает сигнал, названный clicked
, каждый раз, когда кнопку щелкают мышью.
Функция обратного вызова button_clicked
связана с сигналом clicked
виджета кнопки с помощью функции g_signal_connect
:
g_signal_connect(GTK_OBJECT(app), «clicked»,
GTK_SIGNAL_FUNC(button_clicked), «Button 1»);
Обратите внимание на то, что имя кнопки – "Button 1" – передается в функцию обратного вызова как данные пользователя.
Весь остальной добавленный программный код касается виджета кнопки, создаваемой так же, как окно – вызовом функции gtk_button_new_with_label
– функция gtk_widget_show
делает ее видимой.
Для расположения кнопки в окне вызывается функция gtk_container_add
. Эта простая функция помещает GtkWidget
внутрь объекта GtkContainer
и принимает контейнер и виджет как аргументы:
void gtk_container_add(GtkContainer* container, GtkWidget *widget);
Как вы уже знаете, GtkWindow
– потомок или дочерний объект объекта GtkContainer
. поэтому вы можете привести тип вашего объекта-окна к типу GtkContainer
с помощью макроса GTK_CONTAINER
:
gtk_container_add(GTK_CONTAINER(window), button);
Функция gtk_container_add
прекрасно подходит для расположения в окне одиночного виджета, но гораздо чаще вам потребуется для создания хорошего интерфейса размещать несколько виджетов в разных частях окна. У комплекта GTK+ есть специальные виджеты как раз для этой цели, именуемые виджетами упаковочных контейнеров,
Виджеты упаковочных контейнеров
Компоновка GUI исключительно важна для удобства применения интерфейса, и добиться наилучшей компоновки труднее всего. Реальная трудность в размещении виджетов заключается в том, что вы не можете полагаться на наличие у всех пользователей одинаковых размеров окон, тем, шрифтов и цветовых схем. То, что может быть отличным интерфейсом для одной системы, в другой системе может оказаться просто нечитаемым.
Для создания GUI, который выглядит одинаково во всех системах, вам необходимо избегать размещения виджетов на основе абсолютных координат и использовать более гибкую систему компоновки. У GTK+ есть для этой цели виджеты контейнеров. Виджеты-контейнеры позволяют управлять компоновкой виджетов в окнах вашего приложения. Виджеты упаковочных контейнеров (box) представляют очень удобный тип виджета-контейнера. GTK+ предлагает множество виджетов-контейнеров других типов, описанных в интерактивной документации к GTK+.
Виджеты упаковочных контейнеров – невидимые виджеты, задача которых – хранить другие виджеты и управлять их компоновкой или схемой размещения. Для управления размером отдельных виджетов, содержащихся в виджете упаковочного контейнера, вы задаете правила вместо координат. Поскольку виджеты упаковочных контейнеров могут содержать любые объекты GtkWidget
и объект GtkBox
сам является объектом типа GtkWidget
, для создания сложных компоновок можно формировать виджеты упаковочных контейнеров, вложенные один в другой.
У типа GtkBox
существуют два основных подкласса:
□ GtkHBox
– однострочный горизонтальный упаковочный контейнер;
□ GtkVBox
– одностолбцовый вертикальный упаковочный контейнер.
После создания упаковочных контейнеров следует задать два параметра: homogeneous
и spacing
:
GtkWidget* gtk_hbox_new(gboolean homogeneous, gint spacing);
GtkWidget* gtk_vbox_new(gboolean homogeneous, gint spacing);
Эти параметры управляют компоновкой всех виджетов в конкретном упаковочном контейнере. Параметр homogeneous
– логический, если он равен TRUE
, виджеты занимают одинаковую площадь независимо от их индивидуальных размеров. Параметр spacing
задает расстояние между виджетами в пикселах.
После того как упаковочный контейнер создан, добавьте в него виджеты с помощью функций gtk_box_pack_start
и gtk_box_pack_end
:
void gtk_box_pack_start(GtkBox *box, GtkWidget *child,
gboolean expand, gboolean f ill, guint padding);
void gtk_box_pack_end(GtkBox *box, GtkWidget *child,
gboolean expand, gboolean fill, guint padding);
Функция gtk_box_pack_start
вставляет виджеты, начиная от левого края контейнера GtkHBox
и нижнего края контейнера GtkVBox
; функция gtk_box_pack_end
, наоборот, начинает от правого и верхнего краев контейнера. Параметры функций управляют расстоянием между виджетами и форматом каждого виджета, находящегося в упаковочном контейнере.
В табл. 16.1 описаны параметры, которые вы можете передавать в функцию gtk_box_pack_start
или gtk_box_pack_end
.
Таблица 16.1
GtkBox *box | Заполняемый упаковочный контейнер |
GtkWidget *child | Виджет, который следует поместить в упаковочный контейнер |
gboolean expand | Если равен TRUE , данный виджет занимает все доступное пространство, используемое совместно с другими виджетами, у которых этот флаг также равен TRUE |
gboolean fill | Если равен TRUE , данный виджет будет занимать всю доступную площадь вместо использования ее как отступа от краев. Действует, только если флаг expand равен TRUE |
guint padding | Размер отступа вокруг виджета в пикселах |
Давайте теперь рассмотрим эти виджеты упаковочных контейнеров и создадим более сложный пользовательский интерфейс, демонстрирующий вложенные упаковочные контейнеры (упражнение 16.3).
Упражнение 16.3. Макет виджета-контейнера
В этом примере вы спланируете размещение нескольких простых виджетов-меток типа GtkLabel
с помощью контейнеров типа GtkHBox
и GtkVBox
. Виджеты-метки – простые графические элементы, подходящие для вывода коротких текстовых фрагментов. Назовите эту программу container.c:
#include
void closeApp(GtkWidget *window, gpointer data) {
gtk_main_quit();
}
/* Обратный вызов позволяет приложению отменить событие
close/destroy. (Для отмены возвращает TRUE.) */
gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
printf(«In delete_eventn»);
return FALSE;
}
int main (int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *label1, *label2, *label3;
GtkWidget *hbox;
GtkWidget *vbox;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW window), «The Window Title»);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WTNDOW(window), 300, 200);
g_signal_connect(GTK_OBJECT(window), «destroy»,
GTK_SIGNAL_FUNC(closeApp), NULL);
g_signal_connect(GTK_OBJECT(window), «delete_event»,
GTK_SIGNAL_FUNC(delete_event), NULL);
label1 = gtk_label_new(«Label 1»);
label2 = gtk_label_new(«Label 2»);
label3 = gtk_label_new(«Label 3»);
hbox = gtk_hbox_new(TRUE, 5);
vbox = gtk_vbox_new(FALSE, 10);
gtk_box_pack_start(GTK_BOX(vbox), label1, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), label2, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox), label3, FALSE, FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), hbox);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Когда вы выполните эту программу, то увидите следующую схему расположения виджетов-меток в вашем окне (рис. 16.6).
Рис. 16.6
Как это работает
Вы создаете два виджета упаковочных контейнеров: hbox
и vbox
. С помощью функции gtk_box_pack_start
вы заполняете vbox
виджетами label1
и label2
, причем label2
располагается у нижнего края контейнера, потому что вставляется после label1
. Далее контейнер vbox
целиком наряду с меткой label3
вставляется в контейнер hbox
.
В заключение hbox
добавляется в окно и выводится на экран с помощью функции gtk_widget_show_all
.
Схему размещения упаковочного контейнера легче понять с помощью блок-схемы, показанной на рис. 16.7.
Рис. 16.7
Познакомившись с виджетами, сигналами, обратными вызовами и виджетами-контейнерами, вы рассмотрели основы комплекта инструментов GTK+. Но для того чтобы стать программистом, профессионально применяющим GTK+, нужно понять, как наилучшим образом использовать имеющиеся в комплекте виджеты.
Виджеты GTK+
В этом разделе мы рассмотрим API самых популярных виджетов GTK+, которые вы будете применять чаще всего в своих приложениях.
GtkWindowGtkWindow
– базовый элемент всех приложений GTK+. До сих пор вы использовали его для хранения своих виджетов.
GtkWidget
+– GtkContainer
+– GtkBin
+– GtkWindow
Существуют десятки вызовов API GtkWindow
, но далее приведены функции, заслуживающие особого внимания.
GtkWidget* gtk_window_new(GtkWindowType type);
void gtk_window_set_title(GtkWindow *window, const gchar *title);
void gtk_window_set_position(GtkWindow *window, GtkWindowPosition position);
void gtk_window_set_default_size(GtkWindow *window, gint width, gint height);
void gtk_window_resize(GtkWindow *window, gint width, gint height);
void gtk_window_set_resizable(GtkWindow *window, gboolean resizable);
void gtk_window_present(GtkWindow *window);
void gtk_window_maximize(GtkWindow *window);
void gtk_window_unmaximize(GtkWindow *window);
Как вы видели, функция gtk_window_new
создает в памяти новое пустое окно. Заголовок окна не задан и размер и местоположение окна не определены. Обычно вы будете заполнять окно виджетами и задавать меню и панель инструментов перед выводом окна на экран с помощью вызова функции gtk_widget_show
.
Функция gtk_window_set_title
изменяет текст полосы заголовка, информируя оконный менеджер запроса.
Примечание
Поскольку за отображение оформления окна отвечает оконный менеджер, а не библиотека GTK+, шрифт, цвет и размер текста зависят от вашего выбора оконного менеджера.
Функция gtk_window_setposition
управляет начальным местоположением на экране. Параметр position
может принимать пять значений, перечисленных в табл. 16.2.
Таблица 16.2
position | |
---|---|
GTK_WIN_POS_NONE | Окно располагается по усмотрению оконного менеджера |
GTK_WIN_POS_CENTER | Окно центрируется на экране |
GTK_WIN_POS_MOUSE | Расположение окна задаётся указателем мыши |
GTK_WIN_POS_CENTER_ALWAYS | Окно остается отцентрированным независимо от его размера |
GTK_WIN_POS_CENTER_ON_PARENT | Окно центрируется относительно родительского окна (удобно для диалоговых окон) |
Функция gtk_window_set_default_size
задает окно на экране в единицах отображения GTK+. Явное задание размера окна гарантирует, что содержимое окна не будет закрыто чем-либо или скрыто. Для того чтобы изменить размеры окна после его вывода на экран, можно воспользоваться функцией gtk_window_resize
. По умолчанию пользователь может изменить размеры окна, перемещая обычным способом его границу мышью. Если вы хотите помешать этому, можно вызвать функцию gtk_window_set_resizeable
, приравненную FALSE.
Для того чтобы убедиться в том, что ваше окно присутствует на экране и видно пользователю, т.е. не свернуто или скрыто, подойдет функция gtk_window_present
. Она полезна для диалоговых окон, т.к. позволяет убедиться в том, что окна не свернуты, когда вам нужен какой-либо пользовательский ввод. В противном случае, для раскрытия окна на весь экран и его сворачивания у вас есть функции gtk_window_maximize
и gtk_window_minimize
.
GtkEntry
– виджет однострочного текстового поля, который обычно применяется для ввода простых текстовых данных, например, адреса электронной почты, имени пользователя или имени узла сети. Существуют вызовы API, позволяющие задать как считывание введенного текста, так и его максимальную длину в символах, а также другие параметры, управляющие местоположением текста и его выделением.
GtkWidget
+–GtkEntry
Можно настроить GtkEntry
на отображение звездочек (или любого другого определенного пользователем символа) на месте набранных буквенно-цифровых символов, что очень удобно для ввода паролей, когда вы не хотите, чтобы кто-то заглядывал через ваше плечо и читал текст.
Мы опишем большинство самых полезных функций виджета GtkEntry
:
GtkWidget* gtk_entry_new(void);
GtkWidget* gtk_entry_new_with_max_length(gint max);
void gtk_entry_set_max_length(GtkEntry *entry, gint max);
G_CONST_RETURN gchar* gtk_entry_get_text(GtkEntry *entry);
void gtk_entry_set_text(GtkEntry *entry, const gchar *text);
void gtk_entry_append_text(GtkEntry *entry, const gchar *text);
void gtk_entry_prepend_text(GtkEntry* entry, const gchar *text);
void gtk_entry_set_visibility(GtkEntry *entry, gboolean visible);
void gtk_entry_set_invisible_char(GtkEntry *entry, gchar invch);
void gtk_entry_set_editable(GtkEntry *entry, gboolean editable);
Вы можете создать GtkEntry
с помощью функции gtk_entry_new
или при вводе текста фиксированной длины с помощью функции gtk_entry_new_with_max_length
. Ограничение ввода определенной длиной текста избавляет вас от проверки корректности длины ввода и, возможно, необходимости информировать пользователя о том, что текст слишком длинный.
Для получения содержимого виджета GtkEntry
вызывайте функцию gtk_entry_get_text
, которая возвращает указатель const char
, внутренний по отношению к GtkEntry
(G_CONST_RETURN
– макрос, определенный в библиотеке GLib). Если вы хотите изменить текст или передать его в функцию, которая может его модифицировать, следует скопировать строку с помощью, например, функции strcpy
.
Вы можете вручную задавать и изменять содержимое виджета GtkEntry
, применяя функции _set_text
, _append_text
и _modify_text
. Учтите, что они принимают указатели const.
Для применения GtkEntry
в качестве поля ввода пароля, которое отображает звездочки на месте символов, воспользуйтесь функцией gtk_entry_set_visibility
, передав ей параметр visible
со значением FALSE
. Скрывающий символ можно изменить в соответствии с вашими требованиями с помощью функции gtk_entry_set_invisible_char
.
Выполните упражнение 16.4.
Упражнение 16.4. Ввод имени пользователя или пароля
Теперь, познакомившись с функциями виджета GtkEntry, посмотрим на них в действии в небольшой программе. Программа entry.c будет создавать окно ввода имени пользователя и пароля и сравнивать введенный пароль с секретным.
1. Сначала определим секретный пароль, остроумно заданный как secret
:
#include
#include
#include
const char * password = «secret»;
2. У вас есть две функции обратного вызова, которые вызываются, когда уничтожается окно и щелкается мышью кнопка OK:
void closeApp(GtkWidget *window, gpointer data) {
gtk_main_quit();
}
void button_clicked(GtkWidget *button, gpointer data) {
const char *password_text =
gtk_entry_get_text(GTK_ENTRY((GtkWidget *) data));
if (strcmp(password_text, password) == 0)
printf(«Access granted!n»);
else printf(«Access denied!n»);
}
3. В функции main
создается, компонуется интерфейс и связываются обратные вызовы с сигналами. Для компоновки виджетов меток и полей ввода примените виджеты-контейнеры hbox и vbox:
int main (int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *username_label, *password_label;
GtkWidget *username_entry, *password_entry;
GtkWidget *ok_button;
GtkWidget *hbox1, *hbox2;
GtkWidget *vbox;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), «GtkEntryBox»);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_windowset_default_size(GTK_WINDOW(window), 200, 200);
g_signal_connect(GTK_OBJECT(window), «destroy»,
GTK_SIGNAL_FUNC(closeApp), NULL);
username_label = gtk_label_new(«Login:»);
password_label = gtk_label_new(«Password:»);
username_entry = gtk_entry_new();
password_entry = gtk_entry_new();
gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);
ok_button = gtk_button_new_with_label(«Ok»);
g_signal_connect(GTK_OBJECT(ok_button), «clicked»,
GTK_SIGNAL_FUNC(button_clicked), password_entry);
hbox1 = gtk_hbox_new(TRUE, 5);
hbox2 = gtk_hbox_new(TRUE, 5);
vbox = gtk_vbox_new(FALSE, 10);
gtk_box_pack_start(GTK_BOX(hbox1), username_label, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox1), username_entry, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox2), password_label, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(hbox2), password_entry, TRUE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 5);
gtk_box_pack_start(GTK_BOX(vbox), ck_button, FALSE, FALSE, 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Когда вы запустите программу, то получите окно, показанное на рис. 16.8.
Рис. 16.8
Как это работает
Программа создает два виджета типа GtkEntry
, username_entry
и password_entry
, а также задает видимость password_entry
, равной FALSE
, чтобы скрыть введенный пароль. Затем она формирует кнопку GtkButton
, с помощью которой вы связываете сигнал clicked
с функцией обратного вызова button_clicked
.
Как только в функции обратного вызова программа извлечет введенный пароль и сравнит его с секретным паролем, на экран выводится соответствующее сообщение.
Обратите внимание на то, что для вставки виджетов в свои контейнеры вы много раз повторили операторы gtk_box_pack_start
. Для сокращения этого повторяющегося программного кода в последующих примерах будет определена вспомогательная функция.