Архивы: по дате | по разделам | по авторам

Создание GUI в Linux. Часть 1

АрхивПрограммазм (архив)
автор : Денис Колисниченко   05.12.2002

Создание графических интерфейсов в Linux с помощью библиотек Qt и GTK.

В этой статье мы поговорим о создании графического интерфейса для вашей Linux-программы. Как вы знаете, средствами одного С нормальный GUI (Graphical User Interface – Графический интерфейс пользователя, ГИП) не построишь, тем более что после Windows пользователь очень требователен не только к наличию этого самого GUI, но еще и к дизайну формы (окна программы). Поэтому без дополнительных библиотек вам не обойтись. Самыми распространенными библиотеками для создания GUI являются библиотеки GTK и Qt. Рекомендуется использовать только эти библиотеки, поскольку велика вероятность того, что они уже будут установлены у пользователя (уж GNOME и KDE установлены почти у всех), чтобы не сложилась такая ситуация, когда размер вашей программы 300К, а используемая нею библиотека «весит» 20М. Вот подумайте, зачем пользователю ваша программа и станет ли он закачивать ее из сети? Конечно, если для вашего шедевра нет аналогов в мире, вы можете изрядно поиздеваться над пользователем, используя нестандартную библиотеку GUI. В первой части этой статьи будет рассмотрена библиотека GTK, а во второй – Qt. Сразу следует заметить, что эта статья – не русскоязычное руководство по библиотекам GTK и Qt. Это, скорее, небольшой обзор возможностей библиотек.

Скорее всего, GTK уже будет у вас установлена, но вам нужно будет установить пакет gtk+-devel, содержащий необходимые файлы для разработки GTK-программ.

Не хочется в этой статье рассматривать банальный пример окошка с кнопкой hello world!. Уж слишком уж это просто, да и этот пример вы сможете найти в документации по Gtk.

Сейчас напишем небольшой конфигуратор, который будет вносить изменения в файл /etc/resolv.conf. Напомню вам формат этого файла:

domain firma.ru
nameserver 192.168.0.1
nameserver 192.168.0.2

Директива domain определяет наш домен, а две директивы nameserver – первый и второй DNS-серверы, соответственно. Вместо директивы domain можно использовать директиву search, но это кому как нравится. Файл может содержать до 4 директив nameserver, но обычно указываются только два сервера DNS, поэтому мы не будем перегружать себя лишней работой. Но файл resolv.conf не главное в нашей статье – ведь мы разрабатываем GUI. Наш конфигуратор не будет вносить изменения в настоящий файл /etc/resolv.conf – для этого нужны права root, можно, конечно, вызвать auth для аутентификации, но мы не будем этого делать, чтобы не усложнять код программы.

Теперь небольшое вступление в GTK. Элементы ГИП – кнопки, поля ввода, переключатели и тому подобное называется виджитами. Если вы когда-нибудь работали в Delphi, виджиты подобны визуальным компонентам Delphi.

Как и в Delphi, основным элементом GUI является окно (форма в Delphi). Виджиты для размещения в окне помещаются в контейнер. В самом окне выравнивать виджиты можно с помощью вертикальных/горизонтальных боксов или же таблиц. Мне больше нравится второй способ, поэтому мы будем использовать именно таблицы.

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

В качестве примера нарисуем кнопку и определим обработчик ее нажатия:

/* Рисуем кнопочку с надписью Hello, All */
button = gtk_button_new_with_label ("Hello, All");

/* При нажатии кнопки будет вызвана функция hello() */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
                        GTK_SIGNAL_FUNC (hello), NULL);

/* Помещаем кнопку в контейнер */
gtk_container_add (GTK_CONTAINER (window), button);

/* Отображаем кнопку. Без этого вы ее просто не увидите. 
Кстати, не забудьте отобразить окно.
 Порядок отображения виджитов особой роли не играет,
  но рекомендуется окно отображать в последнюю очередь */
gtk_widget_show (button);

А вот функция hello():

/* Тип gpointer – это указатель. Тип gint – целое число,
 gchar – символ и т.д. */
 
void hello( GtkWidget *widget, gpointer data )
{
    g_print ("Hello, All\n");
}

Хватит теории, перейдем к практике. На рисунке 1 изображена уже готовая программа. Работает она так. Когда пользователь введет что-нибудь в поле ввода и нажмет Enter, программа отобразит введенный им текст на консоли. Когда пользователь нажмет Ок, введенная им информация будет еще раз выведена на консоль и записана в файл. При нажатии кнопки Quit программа завершит свою работу. Она должна также завершить работу при нажатии кнопки закрытия окна – в GTK программист сам определяет реакции на стандартные кнопки.

Рис. 1. Resolver

Вот текст программы. Внимательно читайте комментарии.

#include <gtk/gtk.h>
#include <stdlib.h>
#include <stdio.h>

gchar *domain, *dns1, *dns2;

/* Массив из трех полей ввода. Первое предназначено
 для ввода имени домена, два вторых – [1] и [2] – для
  ввода IP-адресов серверов DNS */

GtkWidget *edit[3];

/* Наш файл */
FILE *resolv;

/* Функция записи в файл */
void writetofile( GtkWidget *widget,
               gpointer   data )
{

/* С помощью функции gtk_entry_get_text() мы получаем
 введенный пользователем текст из полей ввода */
 
domain = gtk_entry_get_text(GTK_ENTRY(edit[0]));
dns1 = gtk_entry_get_text(GTK_ENTRY(edit[1]));
dns2 = gtk_entry_get_text(GTK_ENTRY(edit[2]));

/* Выводим прочитанный текст на консоль */
g_print ("Domain %s\n", domain);
g_print ("DNS1 %s\n", dns1);
g_print ("DNS2 %s\n", dns2);

/* Перезаписываем файл resolv.conf в текущем каталоге */
if ((resolv = fopen("resolv.conf","w")) == NULL)
{
/* Наверное, нет места на диске или прав маловато... */
g_print ("ERR: Cannot to open resolve.conf file\n");
gtk_main_quit ();
}

/* Запись в файл */
fprintf(resolv,"domain %s\n",domain);
fprintf(resolv,"nameserver %s\n",dns1);
fprintf(resolv,"nameserver %s\n",dns2);
fclose(resolv);

}

/* Эта функция будет запущена, когда пользователь нажмет
 кнопку закрытия окна или кнопку Quit */
 
gint delete_event( GtkWidget *widget,
                   GdkEvent  *event,
                   gpointer   data )
{
/* Функция gtk_main_quit() используется для завершения
работы GTK-программы.
 Не нужно для этого использовать exit() */
 
    gtk_main_quit ();
    return(FALSE);
}

/* Когда пользователь введет текст и нажмет Enter, введенный
 им текст будет выведен на консоль */
 
void enter_callback( GtkWidget *widget,
                     GtkWidget *entry )
{
  domain = gtk_entry_get_text(GTK_ENTRY(entry));
  printf("Domain: %s\n", domain);
}


int main( int   argc,
          char *argv[] )
{

    GtkWidget *window; /* Окно */
    GtkWidget *button; /* Кнопка */
    GtkWidget *table; /* Таблица для размещения виджитов */
    GtkWidget *label; /* Надпись */
/* Как видите, все виджиты одного типа – GtkWidget, поэтому 
мы могли бы обойтись даже тремя виджитами – для окна,
 таблицы и для всех остальных элементов GUI*/
 
    int i;

    /* Инициализация любой GTK-программы */
    gtk_init (&argc, &argv);

    /* Создаем новое окно */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    /* Устанавливаем заголовок окна */
    gtk_window_set_title (GTK_WINDOW (window), "Resolver");

    /* Устанавливаем реакцию на кнопку закрытия окна.
     Сигнал – delete_event
       Вызываем функцию delete_event(), которая описана выше */ 
       
    gtk_signal_connect (GTK_OBJECT (window), "delete_event",
                        GTK_SIGNAL_FUNC (delete_event), NULL);

    /* Устанавливаем рамку окна */
    gtk_container_set_border_width (GTK_CONTAINER (window), 20);

    /* Создаем таблицу 3x3 */
    table = gtk_table_new (3, 3, TRUE);

    /* Помещаем таблицу в контейнер. Обязательно! */
    gtk_container_add (GTK_CONTAINER (window), table);

    /* Рисуем надписи, помещаем их в ТАБЛИЦУ и отображаем.
     Обратите внимание, что в этом случае нам не нужно
      объявлять отдельную переменную для каждой надписи*/
      
    label = gtk_label_new("Domain: ");
    /* О координатах ячеек поговорим после этого листинга */
    gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 0, 1);
    gtk_widget_show (label);

    label = gtk_label_new("DNS #1: ");
    gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 1, 2);
    gtk_widget_show (label);

    label = gtk_label_new("DNS #2: ");
    gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 2, 3);
    gtk_widget_show (label);

    /* Заполняем наш массив полей ввода. По аналогии с Delphi, я назвал
 	массив edit[]*/
    for(i=0; i<3; i++) 
    { 
    /* Новое поле */
    edit[i] = gtk_entry_new();
    /* Если забыть этот оператор, пользователь ничего не сможет ввести */
    gtk_entry_set_editable(GTK_ENTRY(edit[i]), 1);
    /* Определяем одну для всех реакцию на сигнал activate – нажатие Enter*/
    gtk_signal_connect(GTK_OBJECT(edit[i]), "activate",
                       GTK_SIGNAL_FUNC(enter_callback),
                       edit[i]);
    /* Помещаем edit[i] в таблицу */
    gtk_table_attach_defaults (GTK_TABLE(table), edit[i], 1, 2, i, i+1);
    /* Показываем */
    gtk_widget_show (edit[i]);
    }
    
    /* Создаем кнопку "Ok", помещаем в таблицу,
     определяем реакцию на нажатие и показываем */
    button = gtk_button_new_with_label ("Ok");
    gtk_table_attach_defaults (GTK_TABLE(table), button, 2, 3, 0, 1);
    gtk_signal_connect(GTK_OBJECT(button),"clicked",GTK_SIGNAL_FUNC(writetofile),NULL);
    gtk_widget_show (button);

    /* Тоже самое для кнопки Quit */
    button = gtk_button_new_with_label ("Quit");
    gtk_table_attach_defaults (GTK_TABLE(table), button, 2, 3, 2, 3);
    gtk_signal_connect(GTK_OBJECT(button),"clicked",GTK_SIGNAL_FUNC(delete_event),NULL);
    gtk_widget_show (button);

    gtk_widget_show (table);   /* Показываем таблицу */
    gtk_widget_show (window); /* Показываем окно */

    /* Запускаем GTK-программу */
    gtk_main ();

    return 0;
}

Я старался писать подробные комментарии, но все же кое-что осталось в тумане. Это координаты ячеек. Рассмотрим нашу таблицу 3х3:

0	1	2	3
 Domain   Поле      Ок
1
 DNS1     Поле
2
 DNS2     Поле      Quit
3

Сначала указываются координаты по X, затем – по Y. Вот координаты кнопки Ok: 2,3,0,1. Это означает, что кнопка будет расположена в последнем столбике (2,3), но в первом ряду (0,1). Чтобы было понятнее: ОК по Х находится между 2 и 3, а по Y – между 0 и 1.

Теперь откомпилируем нашу программу:

gcc -g resolv.c -o resolv `gtk-config --cflags` `gtk-config --libs`

Можно не использовать опцию -g, добавляющую отладочную информацию – размер файла станет меньше. Программа gtk-config сообщает компилятору всю необходимую информацию о библиотеке gtk. Обратите внимание на директиву #include <gtk/gtk.h>. Обычно файлы заголовков gtk находятся в другом каталоге, например, gtk-1.2, но это совсем не имеет значения – все необходимые параметры укажет программа gtk-config.

У вас некорректно отображаются русские названия надписей и кнопок? Эта проблема очень быстро устраняется с помощью GNOME Control Center – вам всего лишь нужно выбрать другой шрифт.

Вот теперь этот небольшой обзор можно считать полным. Конечно, я не рассмотрел еще много чего – переключатели, графические кнопки (аналог SpeedButton в Delphi), ..., но помните – это всего лишь статья, а не руководство по GTK! Если вы заинтересовались, прочитайте документацию по GTK – file:/usr/share/doc/gtk+-devel-1.2.10/html/gtk_tut-2.html#ss2.1 Ваши вопросы и комментарии можно отправлять по адресу dhsilabs@mail.ru.

© ООО "Компьютерра-Онлайн", 1997-2024
При цитировании и использовании любых материалов ссылка на "Компьютерру" обязательна.