Основы GTK+, Часть 2: Как использовать GTK+

Мацей Катафьяж, Сергей Безденежных

Часть 1

Это вторая статья из цикла статей под общим названием «Основы GTK+». На примере простого приложения GTK+, написанного на языке C, а затем того же приложения, написанного на языках Python и C#, она познакомит вас с основами программирования с использованием GTK+. В конце вы найдете обзор нескольких полезных программ, которые помогут разрабатывать приложения GTK+ лучшие и быстрее.

Эта статья рассчитана на то, что Вы знакомы с основными понятиями объектно-ориентированного программирования, такими как классы, объекты, методы и наследование. Дополнительно необходимо понимание основ синтаксиса языка C.

Анатомия приложения GTK+ на языке С

Я думаю, что код лучше обсуждать на примере. В этой статье в качестве примера я использую небольшую, написанную на языке С, программу, которая называется HelloWorld. Хотя код этой программы короток и бесполезен, он показывает наиболее интересные концепции, с которыми следует познакомиться, прежде чем начинать программировать с использованием инструментария GTK+ (Листинг 1).

Листинг 1. Приложение Hello World в GTK+

				
#include <gtk/gtk.h>
#include <libintl.h>

#define _(x) gettext (x)
#define N_(x) (x)

#define GETTEXT_PACKAGE "gtk-hello"
#define LOCALEDIR "mo"

static char *greetings[] = { "Hello World",
			     "Witaj świecie",
			     "世界に今日は" };

static char* choose_greeting ()
{
  return greetings[g_random_int_range (0, G_N_ELEMENTS (greetings))];
}

static void cb_button_click(GtkButton *button, gpointer data)
{
  GtkWidget *label = GTK_WIDGET(data);

  g_assert(label != NULL);
  gtk_label_set_text(GTK_LABEL (label), choose_greeting());
}

static gboolean cb_delete(GtkWidget *window, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

int main (int argc, char *argv[])
{
  GtkWidget* window, *button, *label, *vbox;
  
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  gtk_init(&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label (_("Hello World"));
  label = gtk_label_new (choose_greeting());
  vbox = gtk_vbox_new(FALSE, 0);

  gtk_container_add(GTK_CONTAINER (window), vbox);
  gtk_container_add(GTK_CONTAINER (vbox), label);
  gtk_container_add(GTK_CONTAINER (vbox), button);

  g_signal_connect(G_OBJECT (window), "delete-event", 
		   G_CALLBACK(cb_delete), NULL);

  g_signal_connect (G_OBJECT (button), "clicked", 
		    G_CALLBACK (cb_button_click), label);

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

Общая картина

Перед тем, как перейти к деталям, рассмотрим по порядку, что происходит, когда Вы запускаете программу HelloWorld:

  1. Происходит инициализация GTK+ и поддержки интернационализации (i18n);
  2. Создаются виджеты;
  3. Виджеты организуются в иерархию, чтобы GTK+ знал, как их отображать на экране;
  4. Подключаются два обработчика сигналов: первый - для выхода из программы по нажатию кнопки закрытия окна и второй - для изменения приветствия по нажатию кнопки на форме;
  5. Окно отображается на экране, а затем для активации главного цикла программы вызывается функция gtk_main();
  6. Главный цикл выполняется до тех пор, пока пользователь не закроет окно, и не будет вызвана функция gtk_main_quit() .

Инициализация

Следующие строки программы инициализируют GTK+ и поддержку интернационализации:

Листинг 2. Инициализация GTK+ и поддержки интернационализации

				
int main (int argc, char *argv[])
{
  GtkWidget* window, *button, *label, *vbox;
  
  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  gtk_init(&argc, &argv);

Объявление функции main знакомо любому программисту, знающему язык C (если Вы не знакомы с языком С, знайте что main - это функция, с которой начинается выполнение программы).

Следующая строка содержит объявление нескольких указателей с типом GtkWidget. GTK+ - это объектно-ориентированный инструментарий, по этой причине в нем используются обычные для объектно-ориентированного программирования приемы. Например, наследование используется, чтобы создать различные виды виджетов. Сам по себе язык С не является объектно-ориентированным, однако GTK+ преодолевает это ограничение путем использования нескольких хитроумных приемов и некоторых свойств языка С. Таким образом, объекты представляются указателями, а тип GtkWidget называется основным классом , от которого образуются все остальные классы. Поэтому выше я объявил переменные типа GtkWidget*.

Следующие три строки - это вызовы, которые нужно расположить в начале программы, чтобы включить в ней поддержку интернационализации интерфейса. Надо заметить, что в настоящих приложениях вам не придется в ручную объявлять LOCALEDIR и GETTEXT_PACKAGE - ваша система сама позаботится об этих объявлениях, однако в нашем примере они помогают понять, что действительно происходит в программе.

В последней строчке вызывается функция gtk_init(). Вызов этой функции обязателен, при этом ей должны быть переданы аргументы, с которыми запущена программа, а ее вызов должен осуществляться перед тем, как будет сделан вызов любой другой функции GTK+. Если вы забудете осуществить вызов этой функции, то получите несколько десятков ошибок от различных подсистем, которые просто не смогут правильно запуститься.

Создание виджетов

В следующих четырех строках осуществляется вызов различных видов функции _new():

Листинг 3. Вызов различных функций _new() functions

				
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label (_("Hello World"));
  label = gtk_label_new (choose_greeting());
  vbox = gtk_vbox_new(FALSE, 0);

 
Замечание по использованиюTOPLEVEL

Увидев в функции gtk_window_new параметр TOPLEVEL , вы, наверное, подумали, что есть и другие типы окон. Без сомнения есть, однако их обычно не используют. Если вы все-таки хотите использовать другой тип, вы должны иметь четкое представление о взаимодействии GTK+ и оконной системы. Так что запомните простое правило: всегда используйте параметр TOPLEVEL.

Как Вы можете предположить, эти функции создают новые виджеты, поэтому они называются конструкторами (constructors) объектов, представляющих собой виджеты. В языке С++ конструкторы обозначаются особым образом и вызываются с использованием специальных служебных слов языка. Однако С не является объектно-ориентированным языком, поэтому в нем нет разницы между вызовом обычной функции и конструктором. Только окончание _new() добавленное к названию функции, свидетельствует о том, что это на самом деле конструктор. Так же есть соглашение, что любой конструктор в пространстве имен gtk_* возвращает указатель на GtkWidget, поэтому, объявляя переменную такого типа, вы можете присвоить ей результат вызова конструктора.

Если вы внимательно посмотрите на несколько конструкторов, то увидите, что они вызываются с различными параметрами, набор которых зависит от типа создаваемого виджета. В частности, конструкор gtk_window_new (GTK_WINDOW_TOPLEVEL) создает новое окно типа TOPLEVEL , этот виджет представляет собой обычное окно с заголовком, кнопкой закрыть и другими добавленными оконным менеджером элементами.

Вызовы конструкторов для переменных label (метка) и button (кнопка), делают в точности то, что от них ожидается. Обратите внимание на знак подчеркивания и скобки ( _() ), окружающие строку, передаваемую в конструктор кнопки. Этот макрос вызывает подпрограмму gettext() и является основой для перевода интерфейса программы. (Чтобы получить подробную информацию по использованию gettext, посмотрите ссылки).

Загадочный конструктор gtk_vbox_new(FALSE, 0) создает вертикальный бокс (Vbox). Хотя этот виджет не отображает даже пикселя на экране, вы вскоре увидите, что он играет решающую роль при расположении элементов управления GTK+.

Определение расположения

Следующие три строки определяют расположение виджетов на экране:

    gtk_container_add(GTK_CONTAINER (window), vbox);
    gtk_container_add(GTK_CONTAINER (vbox), label);
    gtk_container_add(GTK_CONTAINER (vbox), button);
  

Эти строки - вызовы объектно-ориентированных методов, относящихся к типу GtkContainer. Если вы обратитесь к справочной информации по API, то увидите, что GtkContainer является потомком класса GtkWidget, и что всем его методам как первый параметр передается переменная типа GtkContainer*. Считается, что GtkContainer* - это экземпляр (instance) объекта, на которой должен действовать метод. По той причине, что все переменные объявлены с типом GtkWidget*, а компилятор C не поддерживает объектно-ориентированное наследование, необходимо убедить компилятор, что безопасно передать эти переменные функции и ждать от них значение типа GtkContainer*. Эту работу выполняет макрос GTK_CONTAINER(), в нем реализовано безопасное преобразование к типу GtkContainer. Безопасное преобразование к типу (type-safe), означает, что макрос проверяет, может ли быть выполнена необходимая операция над данным типом перед тем, как осуществить преобразование. Если макрос не может выполнить операцию, он выводит предупреждающее сообщение.

Так как в GTK+ используется модель расположения элементов box layout , нет необходимости точно указывать, где на экране будет располагаться виджет. В программе Hello World каждый вызов метода gtk_container_add() говорит программе взять первый параметр или родительский виджет и поместить в него второй параметр или дочерний виджет . Виджет VBox, используемый в нашем примере, - это вид виджета, используемый для расположения дочерних виджетов вертикально в ряд. Так что, когда вы по очереди помещаете внутрь этого виджета метку, а затем кнопку, то кнопка отображается под меткой.

Это все, что вам нужно сделать. Если вы когда-нибудь вздумаете вручную расположить и определить размеры виджетов, используйте зафиксированные позиции (absolute positioning) - модель расположения элементов, используемая в некоторых других инструментариях, например, в Win32. В GTK+ вся работа по расположению и изменению размеров виджетов, выполняется за вас автоматически.

Подключение сигналов к главному циклу

После создания и размещения виджетов наступило время добавить им немного логики. Как в большинстве графических инструментариев, в GTK+ реализована модель с управлением по событиям (event-driven). По существу, она организована вокруг главного цикла (main loop) обработки событий. Главный цикл представляет собой бесконечный цикл, включающий три этапа: проверку наличия событий, их обработку и ожидание. Когда возникает событие, объект, ассоциированный с этим событием посылает сигнал, который сообщает главному циклу о произошедшем событии. Далее главный цикл ищет сигнал во внутренней таблице, где сопоставлены сигналы и обработчики, эти сопоставления иногда называют обратными вызовами (callbacks). В конце-концов главный цикл вызывает найденный обработчик сигнала для сигнала, пришедшего от объекта.

В коде программы HelloWorld, регистрация обратных вызовов выглядит следующим образом:

Листинг 4. Регистрация обратных вызовов

				
  g_signal_connect(G_OBJECT (window),  "delete-event", 
		   G_CALLBACK(cb_delete), NULL);
  g_signal_connect (G_OBJECT(button), "clicked", 
		    G_CALLBACK(cb_button_click), label);
		    

Примечательно, что в GTK+ вы подсоединяете сигналы. Так, в первой строчке функция cb_delete соединена с сигналом delete-event объекта window. Точно так же во второй строчке функция cb_button_click соединена с сигналом clicked объекта button . Обратите внимание на то, что в четвертом параметре второго вызова, передается указатель на нашу метку label, позже вы увидите, как он используется в функции cb_button_click.

Ниже представлена функция cb_delete , которая завершает приложение, когда пользователь закрывает окно.

static gboolean cb_delete(GtkWidget *window, gpointer data)
{
  gtk_main_quit();
  return FALSE;
}

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

Любой обратный вызов для сигнала "delete-event" обязан соответствовать определенному в API прототипу, потому этой функции передается аргумент типа GtkWidget* и указатель на неопределенный тип data (тип gpointer равноценен типу void*). В нашем случае эти аргументы не нужны, и мы их просто игнорируем. Далее в теле функции вызывается функция gtk_main_quit(), она завершает бесконечный главный цикл. В конце, функция возвращает значение логического типа, снова по той же причине, что в прототипе обратного вызова для сигнала delete-event точно определено: функция должна возвратить логический тип. Возвращаемое логическое значение определяет то, какие действия дальше предпримет GTK+. Если функция возвратит значение TRUE, событие будет считаться обработанным, и обработчик события, который по умолчанию для этого вызова удаляет виджет из оконной системы, не будет вызван. Иногда это бывает нужно, например, если надо спросить у пользователя сохранять данные или нет, возвратив TRUE, можно приостановить закрытие окна.

Ниже приведена функция cb_button_click, ее задача по нажатию кнопки изменить приветственное сообщение:

Листинг 5. Функцияcb_button_click

				
static void cb_button_click(GtkButton *button, gpointer data)
{
  GtkWidget *label = GTK_WIDGET(data);

  g_assert(label != NULL);
  gtk_label_set_text(GTK_LABEL (label), choose_greeting());
}

Как вы можете видеть, эта функция похожа на функцию cb_delete, за исключением того, что она ничего не возвращает и вместо аргумента GtkWidget ей передается GtkButton*. В коде функции неопределенный указатель data преобразуется в указатель на GtkLabel. Помните четвертый параметр label, на который мы обратили внимание при регистрации обратных вызовов? Теперь при каждом обратном вызове указатель data будет содержать указатель на созданную нами в начале программы метку. Вы можете использовать аргумент data везде, где нужно передать дополнительную информацию функции обратного вызова. Таким же образом, если нужно получить доступ к объекту, пославшему сигнал, можно использовать первый параметр, которому в нашем случае соответствует аргумент button.

После получения указателя на метку, макрос g_assert проверяет не получилось ли так, что наша метка указывает на значение NULL. Макрос g_assert - это специальная утилита из библиотеки Glib (библиотека полезных типов и программ, активно используемая GTK+), которая прекращает исполнение программы, если не выполняется переданное ей условие, в нашем случае программа остановится, если label будет равно NULL. Поскольку label будет равняться NULL только в случае ошибки программиста, это выражение гарантирует то, что ошибка будет отловлена до того, как программа увидит свет.

Отображение окна

После регистрации обратных вызовов функция gtk_widget_show_all() показывает окно и все его виджеты на экране (Рисунок 1).

Рисунок 1. Программа Hello World на польском и японском
Выполняющееся приложение

Активация главного цикла

После того, как все установлено и отображено на экране, фукция gtk_main() активирует главный цикл. Главный цикл входит в бесконечный цикл ожидания и обработки поступающих сигналов до тех пор, пока кто-нибудь, закрыв окно, не вызовет функцию gtk_main_quit() .

Примечание:Если у вас остались какие-нибудь вопросы по программе, посмотрите код в архиве. Он точно такой же как в статье, но каждой строчке дан подробный комментарий.

Компиляция и запуск

Чтобы скомпилировать эту программу, вам понадобится компилятор С и файлы разработчика (файлы заголовков и библиотеки) для GTK+. Информация о том как их получить приведена в ссылках.

После установки этих файлов, разархивируйте исходный код, перейдите в появившейся каталог и запустите make:

$ tar -xzf gtk_hello.tgz
$ cd gtk_hello
$ make

Примечание:Если вы используете Microsoft® Windows®, вместо запуска make откройте Microsoft Visual Studio™.NET и запустите проект "hello".

Использование GTK+ в других языках программирования

Вы можете использовать инструментарий GTK+ во множестве языков программирования. Чтобы воспользоваться им, вам понадобятся привязки. Привязки (bindings) - это специальные пакеты для определенного языка, которые представляют API GTK+ в форме, понятной этому языку.

Например, я переписал код нашего приложения HelloWorld на языки Python и C#. Чтобы запустить GTK+ c этими языками, в дополнение к системам программирования Python и Mono/.NET вам соответственно понадобятся привязки PyGTK и Gtk#.

Hello World и PyGTK

В листинге 6 показан код приложения Hello World, переписанный на язык Python.

Листинг 6. Приложение Hello World в PyGTK

				
import pygtk
pygtk.require('2.0')
import gtk
import random

greetings = ["Hello World", "Witaj Świecie", "世界に今日は"]
def choose_greeting (greets):
    return greets[random.randint (0, len(greets) - 1)]

def cb_clicked(button, label):
    label.set_text(choose_greeting(greetings))

window = gtk.Window ()
vbox = gtk.VBox ()
button = gtk.Button("Hello World")
label = gtk.Label (choose_greeting (greetings))

window.add(vbox)
vbox.add(label)
vbox.pack_start(button, False, False)

window.connect("delete-event", lambda a,b: gtk.main_quit())
button.connect("clicked", cb_clicked, label)

window.show_all()
gtk.main()

Благодаря краткости Python, эта версия программы получилась немного короче, чем ее двойник на С. Несмотря на это, выглядит она похоже. Хотя код приложения был переписан с использованием выражений Python, интерфейс программирования (API) остался тот же самый.

Hello World и Gtk#

Код приложения Hello World с использованием привязки Gtk# занимает чуточку больше места, чем его версия на языке С, это связано с тем, что синтаксис выражений в языке С# достаточно длинный. Поэтому, я не поместил здесь полный исходный код, если нужно, его можно посмотреть в архиве. Здесь кратко рассмотрены несколько моментов при переносе приложения из языка C в C#:

  class GtkHello : Gtk.Window
  {

Вместо создания нового окна и описания его параметров, вы создаете подкласс класса Gtk.Window и помещаете весь код описания параметров в его конструктор. Этот подход не является характерной чертой Gtk#, он часто используется в С-программах, если нужно создать более одной копии окна. В любом случае, создавать подклассы в C# так просто, что имеет смысл объявить новый класс, даже для одного нового окна, тем более, что структура программ на C# требует от вас объявить хотя бы один класс.

  this.DeleteEvent += new DeleteEventHandler(DeleteCB);
  button.Clicked += new EventHandler(ButtonClickCB);

Как можно видеть, сигналы GTK+ переведены в родную для C# концепцию событий. Имена немного изменены, чтобы лучше следовать соглашениям именования принятым в C#.

Листинг 7. Сигналы GTK+, переведенные в родную для C# концепцию событий

				
  private void DeleteCB (object o, DeleteEventArgs args)
  {
    Application.Quit ();
    args.RetVal = true;
  }

Из-за особенностей модели событий C# прототип для обработчика delete-event немного отличается. Вместо того, чтобы через функцию обратного вызова возвратить значение true, нужно передать его через аргументы args.RetVal. Пара функций gtk_main() и gtk_main_quit() заменены соответственно вызовами методов Application.Run() и Application.Quit().

Полезные инструменты

Существует несколько инструментов, способных значительно облегчить вам жизнь при разработке программ с использованием GTK+. Наиболее полезными считаются Glade, Libglade и Devhelp.

Glade and Libglade

Дизайнер интерфейсов Glade помогает графически создавать интерфейс приложения, вместо того, чтобы в исходном коде описывать в отдельности каждый элемент. Но еще более полезным является второй компонент - библиотека Libglade, она позволяет читать специальный формат XML, который Glade использует при описании интерфейса. С помощью Libglade вы можете создать интерфейс приложения из этого описания, без написания какого-либо кода вообще.

На рисунке 2 показано простое приложение, использующее Libglade для отображения нескольких виджетов.

Рисунок 2. Простое приложение, использующее Libglade
Application using libglade

В листинге 8, показан полный исходный код приложения, изображенного на рисунке 2.

Листинг 8. Исходный код приложения Libglade

				
#include <gtk/gtk.h>
#include <glade/glade.h>

int main (int argc, char *argv[])
{
  GladeXML *ui;
  gtk_init(&argc, &argv);

  /* Read user interface description from glade file */
  ui = glade_xml_new ("glade_hello.glade", "main_window", NULL);
  /* Automatically connect signals */
  glade_xml_signal_autoconnect(ui);
  gtk_main();

  return 0;
}    

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

Если вы хотите более подробно изучить, как построена эта программа, вы можете найти ее, также как все другие примеры из этой статьи, в приложении.

Devhelp

Браузер документации Devhelp разработан для чтения документации в формате, создаваемом программой gtk-doc (стандартной утилитой для создания документации GTK+), которая так же используется в некоторых других родственных проектах, таких как Pango и GNOME. С помощью Devhelp вы можете быстро найти функцию и посмотреть установленную по ней документацию, он помогает получать необходимую информацию намного быстрее. Devhelp - это приложение GNOME, поэтому чтобы запустить его, вам понадобится POSIX-совместимая операционная система(напр. Linux® или Sun Solaris), на которой будет работать GNOME и его библиотеки. Однако для просмотра документации вам не обязательно запускать сам GNOME.

Если вы используете другую платформу, существует множество других путей чтения документации GTK+ . В проекте Mono, есть навигатор Monodoc, который обычно поставляется вместе со справочной информацией по Gtk#. Установочные пакеты GTK+ для Windows также включают документацию в формате, пригодном для чтения средствами Windows, включая Visual Studio®. В конце концов, всегда существует возможность обратиться к документации в сети с помощью веб-обозревателя, однако использование специального инструмента всегда предпочтительнее, так как он работает быстрее и обладает специальными функциями, полезными при поиске в документации.

В следующей статье

В этой статье вы изучили основные приемы программирования с использованием GTK+ . Также вы увидели, как использовать GTK+ в разных языках программирования, оперируя выражениями, специфичными для этих языков, но при этом, придерживаясь общей концепции создания приложений с GTK+. И в конце вы ознакомились с несколькими полезными инструментами, которые помогут разрабатывать приложения GTK+ лучшие и быстрее.

В последней статье этого цикла, вы ближе познакомитесь с другим аспектом разработки приложений с использованием GTK+ - установкой. В ней будут детально проанализированы возможности в этой области, включая вопросы переносимости и простоты установки. В заключение вы посмотрите на GTK+ с более широкой точки зрения, как на проект с активным сообществом, которое может помочь вам создавать приложения, лучше отвечающие потребностям ваших пользователей.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=4227