Glade# Intro

Полезные советы и программы от пользователей нашего форума.

Модератор: Модераторы разделов

Аватара пользователя
oav
Бывший модератор
Сообщения: 296

Glade# Intro

Сообщение oav »

1. О Glade
Все современные программы имеют богатый графический интерфейс пользователя, который часто меняется и имеет сложную внутреннюю структуру. При написании программ на 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 файл можно генерировать по определенным правилам и в run-time менять. Скажем, на основе созданной таблице в БД, генерировать XML описывающий форму заполнения записей, что часто используется в таких системах как АРМ. Также файл можно загружать удаленно и др. способы обработки/хранения xml файлов. Если сюда добавить все мощь отражения .NET (особенно включая компиляцию кода "на лету"), получается совершенно новый способ разработки и проектирования ПО, более гибкий и другие слова, которые так любят писать менеджеры крупных корпораций ;), только в этом случае, на мой взгляд это действительно так и заслуживает пристального внимания.

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

К сожалению, редактор Glade пока не поддерживает работу с несколькими проектами (аналог workspace или solution в VisualStudio), но есть над чем поработать или , на худой конец, написать заявку в список рассылки проекта ;).

4. Ссылки
Проект Mono
.NET Framework "от создателей"
Mono C# IDE
Описание механизма отражения .NET
Страница Glade
Спасибо сказали:
Аватара пользователя
edoc_modnar
Бывший модератор
Сообщения: 1638
Статус: Форум больше не посещаю

Re: Glade# Intro

Сообщение edoc_modnar »

Ты бы лучше здесь прямо статью поместил, посмотри, все так делают...
Просто очень многие здесь (в т.ч. и я) хреново относимся к ентому ресурсу... и посещать его лишний раз не охота ;).
So long, and thanks for all the fish.
Douglas Adams, The Hitchhiker's Guide to the Galaxy
Спасибо сказали:
Аватара пользователя
oav
Бывший модератор
Сообщения: 296

Re: Glade# Intro

Сообщение oav »

(random_code @ Thursday, 08 July 2004, 17:15) писал(а):Ты бы лучше здесь прямо статью поместил, посмотри, все так делают...
Просто очень многие здесь (в т.ч. и я) хреново относимся к ентому ресурсу... и посещать его лишний раз не охота ;).

да, я уже об этом подумал - как доберусь до ее исходников - переделаю
Спасибо сказали: