Все современные программы имеют богатый графический интерфейс пользователя, который часто меняется и имеет сложную внутреннюю структуру. При написании программ на Gtk# пользователю необходимо писать код размещения и настройки всех элементов GUI что является очень утомительным и не благодарным занятием. К тому же данный подход весьма не эффективен по двум причинам: во первых, смотря на код вы не можете увидеть какую форму GUI он представляет и, соответственно, не понимаете к чему это относиться; во-вторых, при изменении внешнего вида GUI, вам необходимо не менять GUI-элементы,а менять код, их представляющий. т.е. несоответствие задачи-реализации. Особенно остро это выглядит в свете большого распостранения RAD систем, где GUI "рисуется" мышью в легкой и эффективной форме. А как бы хорошо было, если бы создании внешнего вида программы было отделено от ее функциональности ( как например html оформление отделают от, скажем, PHP-функциональности). Тут на сцену выходит Glade.
Glade - это конструктор пользовательского интерфейса для Gtk и GNOME. Именно Glade позволит нам отделить внешнее представление программы от ее логики работы.
Ниже на рисунке показана архитектура работы с Glade#:

2. Создание интерфейса в Glade.
Статья посвящена использованию libGlade в проектах на Gtk#, поэтому по использованию редактора интерфейса отсылаю вас на страницу в интернете: glade.gnome.org, где можно ознакомится со многими аспектами работы с программой.
Отмечу лишь, что при создании объектов необходимо ответственно подходить к полю Name - то что вы здесь напишете, будет использоваться в коде при обращении к объекту и желательно сюда записывать имя соответствующее предназначению объекта (соответственно, если объект в коде вам не понадобиться имя не имеет значения).
3. Первая программа.
От теоретических изысканий переходим к практической деятельности.
Создадим простое окно в Glade, показанное на рисунке:

Программа будет выводить MessageBox с текстом, введенным в entryName и показывать диалоговое окно About.
После создания интерфейса, нажмите на File->Save (только не делайте Build! - это приведет к генерации кода на C ).
После этого в каталоге проекта появится файл имя_проекта.glade ( в моем случае sample.glade ). Имя файла можно изменить в настройках проекта.
Теперь напишем код программы:
Код: Выделить всё
using System;
using Gtk;
using GtkSharp;
public class SampleDlg
{
public static void Main (string[] args)
{
new SampleDlg( args );
}
private Glade.XML gxml;
public SampleDlg( string[] args )
{
Application.Init();
gxml = new Glade.XML ("sample.glade", "SampleMainWnd", null);
gxml.Autoconnect (this);
Application.Run();
}
/* Connect the Signals defined in Glade */
public void OnWindowDelete(object o, DeleteEventArgs args)
{
Application.Quit ();
args.RetVal = true;
}
public void OnQuit(System.Object obj, EventArgs e)
{
Window wnd = (Window)gxml.GetWidget( "SampleMainWnd" );
wnd.Destroy();
Application.Quit();
}
public void OnAbout(System.Object obj, EventArgs e)
{
}
public void OnShowName(System.Object obj, EventArgs e)
{
MessageDialog md= new MessageDialog( SampleMainWnd, DialogFlags.DestroyWithParent,
MessageType.Info, ButtonsType.Close, "Your name is: " + entryName.Text );
md.Run();
md.Destroy();
}
[Glade.Widget]
Entry entryName;
[Glade.Widget]
Window SampleMainWnd;
}
И Makefile:
Код: Выделить всё
all: sample.exe
%.exe: *.cs
mcs -o $@ $^ -r gtk-sharp -r glade-sharp
clean:
rm -f *.exe
Пока опустим реализацию диалога About, но разберемся как это работает.
Все классы библиотеки Glade находятся в пространстве имен Glade. Самый важный и часто используемый класс - Glade.XML, который и выполняет все работу по загрузке xml-файла и связыванию полученного GUI с реальным кодом.
У Glade.XML опеределено несколько удобных конструкторов, здесь использовался конструктор, который принимает путь к файлу на диске, есть же такие как инициализация из памяти, из ресурсов и др.
gxml = new Glade.XML ("sample.glade", "SampleMainWnd", null);
Здесь первый аргумент - это путь к файлу, второй - корень xml файла, т.е. объект, с которого будет создаваться top-level окно и нижележащие. В одном xml-файле может быть описано множество top-level окон, поэтому мы явно указываем что нам необходимо окно SampleMainWnd. Следующий аргумент связан с интернациализаций и в этом примере не используется.
gxml.Autoconnect (this);
В этом вызове происходит "магия" glade - автоматическая привязка сигналов виджетов и объектов GUI к реальным функциям обработчикам и членам класса. Glade связывает по имени, т.е. названия обработчиков событий должны полностью совпадать с названиями, введенными на этапе дизайна интерфейса в Glade.
Стандартный прототип обработчика события Gtk такой:
public void OnEventHandler(System.Object obj, EventArgs e);
Где obj - это объект, инициировавший событие, а e - список параметров связанный с событием. Здесь EventArgs - это базовый класс для всех Gtk событий, большинство событий имеют свой класс, наследуемый от EventArgs (например обработчик окна delete передает параметры в DeleteEventArgs ).
Как получить ссылку на виджет, если они все скрыты внутри glade? Существует несколько способов, в этом примере приведено 2. Первый, в обработчике OnQuit:
Window wnd = (Window)gxml.GetWidget( "SampleMainWnd" );
wnd.Destroy();
Здесь мы динамически запрашиваем ссылку на виджет по имени. Имена соответствуют именам в xml-описании. Для этого способа нам необходимо иметь ссылку на объект Glade.XML .
Второй способ более удобный, он основывается на использовании атрибута Glade.WidgetAttribute. Это простой атрибут, но который делает очень удобное связывание виджетов с членами класса. Использовать атрибут просто:
[Glade.Widget]
Entry entryName; Таким образом мы описываем что член класса entryName будет связан с неким виджетом из созданного Glade интерфейса. Каким именно виджетом? Посмотрим исходный код атрибута:
Код: Выделить всё
[AttributeUsage (AttributeTargets.Field)]
public class WidgetAttribute : Attribute
{
private string name;
private bool specified;
public WidgetAttribute (string name)
{
specified = true;
this.name = name;
}
public WidgetAttribute ()
{
specified = false;
}
}
Итак, что видно по этому коду. Во-первых, очевидно что атрибут можно применять только к членам класса. Второй момент более любопытен, атрибут может быть специфицированным или нет (specified). Что бы это значило? Ответ можно найти в исходном коде Glade, в месте где происходит привязка виджетов к членам класса (следующий код предполагает значение системы отражения в .NET Framework. Подробнее об отражении можно почитать по ссылкам в конце статьи):
Код: Выделить всё
private void BindFields (object target, Type type)
{
System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
if (target != null)
flags |= System.Reflection.BindingFlags.Instance;
else
flags |= System.Reflection.BindingFlags.Static;
System.Reflection.Fi eldInfo[] fields = type.GetFields (flags);
if (fields == null)
return;
foreach (System.Reflection.FieldInfo field in fields)
{
object[] attrs = field.GetCustomAttributes (typeof (WidgetAttribute), true);
if (attrs == null || attrs.Length == 0)
continue;
// The widget to field binding must be 1:1, so only check
// the first attribute.
WidgetAttribute widget = (WidgetAttribute) attrs[0];
if (widget.Specified) field.SetValue (target, GetWidget (widget.Name), flags, null, null);
else field.SetValue (target, GetWidget (field.Name), flags, null, null);
}
}
Внимательно посмотрев на этот достаточно простой код, становиться ясно как используется свойство атрибута specified. Пройдемся по порядку по коду. Функция BindFields (привязка полей) принимает 2 параметра: target - это тот объект, который мы передали в Glade.XML.Autoconnect (this). Второй параметр - это тип объекта (в текущей реализации описывает тип target), по которому будет производиться поиск членов с атрибутом Glade.Widget.
Идем дальше. Отражение (reflection) будет выполняться по всем открытым и закрытым членам типа. Если ссылка на target действительна ( не null), то будет производится поиск по членам экземпляра объекта (target), иначе по членам типа (instance).
Далее идет цикл по всем членам и проверка, помечены ли они атрибутом Glade.WidgetAttribute. Когда программа находит такой член, он проверят специфицирован ли атрибут. Тут-то и можно понять как используется это свойство : если атрибут специфицирован (т.е. у атрибута есть заполненное свойство Name), то поле будет связано с виджетом с именем WidgetAttribute.Name через знакомый уже нам метод Glade.XML.GetWidget.
Иначе, поле будет связано с виджетом, имя которого совпадает с именем члена. Все достаточно просто и универсально.
В нашем примере все члены имели не специфицированные атрибуты, хотя если нам не нравится имя, скажем entryName, то мы-бы могли написать следующее определение поля, который бы также корректно связал член с виджетом.
[Glade.Widget( "entryName" )]
Entry m_EntryWidget;
2.1 Добавляем диалог About в проект
Чтобы добавить диалог "О программе", необходимо создать новое top-level окно в Glade. Я его назвал aboutDlg.
Далее, допишем код в существующий проект:
Код: Выделить всё
class AboutDlg
{
public AboutDlg()
{
Glade.XML myXML = new Glade.XML ("calculator.glade", "aboutDlg", null);
myXML.Autoconnect (this);
}
public void OnClose(System.Object obj, EventArgs e)
{
aboutWindow.Destroy();
}
[Glade.Widget]
private Gtk.Window aboutDlg;
}
Это определение класса я ввел в этот-же файл, в реальных-же проектах, лучше вынести код в отдельный файл.
Напишем обработчик, который показывает этот диалог:
Код: Выделить всё
public void OnAbout(System.Object obj, EventArgs e)
{
AboutDlg ad = new AboutDlg();
}
2.2 Размещение файла XML-описания GUI.
До сих пор мы полагались что XML-описание интерфейса находится вместе с программой. Такой способ имеет право быть, но тогда нам необходимо следить за тем, чтобы этот файл всегда распространялся вместе с программой и она могла до него добраться. Всех этих неприятностей можно избежать, воспользовавшись моделью ресурсов .NET Framework.
Ресурс - это некий файл, который можно поместить прямо в файл сборки и которому можно в дальнейшем обращаться через объект System.Reflection.Assembly методом GetManifestResourceStream. Обращение к ресурсу идет по имени.
Данного простотого описания нам уже достаточно для задачи спрятать XML файл внутрь сборки.
Сначала необходимо поправить Makefile, указав компилятору на факт включения файла как ресурса:
Код: Выделить всё
all: sample.exe
%.exe: *.cs
mcs -o $@ $^ -r gtk-sharp -r glade-sharp -resource:sample.glade
clean:
rm -f *.exe
Как можно видеть, изменения не большие - просто добавили параметр компиляции resource и указали имя файла.
Теперь необходимо изменить конструктор инициализации Glade.XML, указав ему что файл необходимо грузить из ресурсов:
gxml = new Glade.XML ( null , "sample.glade", "SampleMainWnd", null);
Вот и все! Теперь можно не держать файл-описание вместе с программой что очень удобно раз и избавляет от таких ошибок, как нежелательная модификация файла пользователем.
3. Небольшое резюме: Использование Glade# в реальных проектах.
Плюсы рассмотренной технологии создания пользовательского интерфейса очивидны: мы отделяем дизайнерскую работу от программирования; полученной код свободен от реализации GUI и, соответственно, более понятен, прозрачен и легче в сопровождении (в Microsoft Visual Studio эту задачу решают с помощью folding'a, встроенного в редактор используя директиву #region - гениально


Любая современная более-менее сложная программа обладает большим количеством графического интерфейса, в связи с чем хранения всего описания в одном файле неудобно по нескольким причинам: во первых, разбор и валидация xml файла (даже с указанием корня) - это достаточно долгая операция, что может сказаться на производительности; во-вторых, когда весь пользовательский интерфейс описан в одном файле - это несет много неудобств и проблем с модификацией файла несколькими людьми в команде разработчиков.
Делая вывод из выше сказаного, можно посоветовать каждое отдельное top-level окно записывать в отдельный файл. Все, полученные таким образом файлы, положить, скажем в каталог UIXML и на базе содержащихся там файлов создавать отдельную сборку с ресурсами.
К сожалению, редактор Glade пока не поддерживает работу с несколькими проектами (аналог workspace или solution в VisualStudio), но есть над чем поработать или , на худой конец, написать заявку в список рассылки проекта

4. Ссылки
Проект Mono
.NET Framework "от создателей"
Mono C# IDE
Описание механизма отражения .NET
Страница Glade