Журнал LinuxFormat - перейти на главную

LXF91:Mono

Материал из Linuxformat
Перейти к: навигация, поиск


Mono-Maния Программирование на современной платформе для начинающих


Содержание

Mono: Создаем GTK-приложение

При вашем знании Mono обычные текстовые программы писать уже скучно. Пол Хадсон поможет разобраться с новым проектом, использующим графический интерфейс.

Мы дошли всего до пятого урока, а мне уже не терпится показать вам магию GTK#... Вообще-то я бы и погодил с показом, но столько читателей просили меня об этом – не игнорировать же народные массы! Надеюсь, вам понравились эксперименты с Mono – ведь это очень весело. Но, по моему мнению, стоит влезть в библиотеку GTK, как начнутся первые проблемы. Видите ли, GTK писали люди, не любившие принципы объектно-ориентированного программирования, но попытавшиеся реализовать их на С. Да еще он безнадежно усложнен и почти непознаваем для новичка. Но вам повезло: я рядом!

На первых четырех уроках вы создали программы Hello World, утилиту для поиска файлов, RSS-агрегатор и простой интерфейс для Beagle. На этом уроке, чтобы целиком сконцентрироваться на GTK, мы возьмем готовый код из RSS-проекта и создадим графический RSS-агрегатор, который я назову Chomp. GTK не столько сложен, сколько муторен – если вы что-то не вполне поняли, вернитесь назад на пару абзацев: нить рассуждений потерять очень легко.

Удар от Stetic

Как я уже сказал, GTK написан на С, но к нему существует интерфейс Mono/C#, вполне предсказуемо названный GTK#. Это очень тонкая обертка вокруг кода С, а значит, работать с ней довольно нудно. Но одну часть нудной никак не назовешь: это Stetic – инструмент для построения графических интерфейсов пользователя в стиле Drag&Drop, входящий в состав MonoDevelop. Если вы помните первый урок данной серии, мой дистрибутив – Fedora Core 6, и те, кто тоже его установил, уже могут использовать MonoDevelop со Stetic. Если у вас другой дистрибутив, убедитесь, что MonoDevelop в нем версии 0.12 или старше.


Первым делом нарисуем наш интерфейс с помощью Stetic. Никакой функциональности это не даст, зато даст представление о том, как будет выглядеть готовое приложение, а я всегда считал, что это позволяет шагнуть далеко вперед. Другая причина, почему стоит сначала спроектировать интерфейс – вы сразу же увидите, будет ли ваш проект неуклюж или невразумителен, и, в случае чего, заранее примете меры по редизайну интерфейса. Помните, что у лучших проектов интерфейс пользователя разрабатывается очень тщательно – не зацикливайтесь исключительно на написании кода в надежде, что GUI сложится само собой!

Прежде чем приступать, полностью обновите версию Fedora, потому что Stetic – сравнительно молодой инструмент, и лучше использовать свежую версию MonoDevelop для минимизации числа возможных отказов. Да почаще сохраняйте результаты, поскольку Stetic не страдает излишней стабильностью.

Итак, аккуратно выполните инструкции по работе в MonoDevelop, приведенные далее и продолжите чтение.

Часть 1 Создание интерфейса пользователя

Шаг 1

1 Создайте новый проект Выберите проект С# из области шаблонов, а затем – Gtk# 2.0 Project, справа. Назовите его chomp, снимите галочку с Create Separate Solution Subdirectory и нажмите New.

Шаг 2

2 Откройте MainWindow Откройте дерево пользовательского интерфейса в панели Solution и дважды щелкните на MainWindow. Откроется MainWindow.cs в режиме Designer.

Шаг 3

3 Добавьте четыре области В палитре Widgets пройдите до Containers и перетащите VBox на окно. Щелкните на нем правой кнопкой и выберите Vbox1 > Insert Before для четырех областей.

Шаг 4

4 Преобразуйте области Перетащите главное меню, панель инструментов, Hpaned и строку состояния во все четыре области, начиная с верхней. Разделите Hpaned на две примерно равные части, как показано на рисунке.

Шаг 5

5 Новый Notebook Перетащите VBox в левую часть Hpaned. Нажмите на ней и выберите Delete. Перетащите Notebook и Button на верхнюю и нижнюю стороны VBox.

Шаг 6

6 TreeView и Calendar Перетащите Tree View на вкладку Page1 компонента Notebook. Щелкните на нем правой кнопкой, выберите Notebook1, затем Insert Page After. На вкладке Page2 вставьте Calendar.

Шаг 7

7 Добавьте полосы прокрутки В большом пространстве справа от элемента Hpaned вставьте Scrolled Window, затем поместите в него Text View. Пользователи смогут прокручивать текст.

Шаг 8

8 Организуйте меню Дважды щелкните на пустом меню, наберите /‘File’, затем нажмите Enter. Таким образом, вы создадите подменю – наберите в нем ‘Quit’. Создайте меню Help с подменю About.

Шаг 9

9 Кнопки панели инструментов Дважды щелкните по панели инструментов, выберите Select Icon, затем gtk-new. Добавьте две другие кнопки: одну с gtk-refresh, другую с gtk-delete.

Часть 2 Реализуем функционал программы

Создание интерфейса позади, но для готового GUI еще кое-чего нехватает. Нужно сделать три вещи. Во-первых, назначить нормальные имена виджетам: пока что они называются button1, textview2 и т.д. Во-вторых, связать виджеты с полями: тогда мы сможем обращаться к ним из кода на C#. И в-третьих, изменить свойства виджетов, чтобы они выглядели как надо и делали то, что мы хотим.


Имя виджета – это просто одно из свойств, поэтому задачи 1 и 3 выполняются через панель Widget Properties, находящейся в правом нижнем углу окна MonoDevelop. Вы увидите кнопку Bind To Field в верху окна, которое только создали – я вам скажу, когда она понадобится.

Вот список требуемых изменений:
  • Нажмите на кнопку New на панели инструментов toolbar (не на самой панели!) и измените ее имя на btnNew.
  • Измените имя кнопки Refresh на btnRefresh.
  • Измените имя кнопки Delete на btnDelete.
  • Измените имя Tree View на tvFeeds. Нажмите Bind To Field.
  • Снимите галочку с Show Day Names для Calendar (это его немного уменьшит). Нажмите Bind To Field.
  • Измените имя Text View на txtFeed. Нажмите Bind To Field. Измените Hight Request на 300, а Width Request на 400 (теперь Text View не будет занимать весь экран).
  • Измените имя кнопки под компонентом notebook на btnRefreshAll.
  • Измените текст для страниц компонента notebook на By Feed для вкладки с Tree View и на By Date для вкладки с Calendar.

Добавим реализацию

Вот и все – с интерфейсом пользователя покончено, можно сфокусироваться на реализации методов. Наберем кое-какой код прямо в файле MainWindow.cs, а также установим обработчики сигналов для MainWindow.cs в Stetic. Сигналы – это внутренние GTK-сообщения, которые посылаются, когда что-нибудь происходит: например, пользователь нажимает кнопку или клавишу. По умолчанию, большая часть сигналов ничего не делает, но мы можем на них подписаться, назначив каждому обработчик с помощью Stetic – он даже создаст за нас базовый метод!


Прежде всего обработаем событие, возникающее, когда пользователь выделяет что-то в Tree View нашей программы. Этот компонент будет использоваться для отображения списка RSS-лент, на которые подписан пользователь, поэтому при выборе пользователем ленты из списка Chomp надо скачать последнюю версию RSS-файла и отобразить его в Text View справа. Щелчки пользователя можно обработать, отлавливая сигнал RowActivated – щелкните на Tree View, затем на панели Widget Properties перейдите на вкладку Signals. Найдите сигнал RowActivated и дважды щелкните там, где написано Click Here To Add A New Handler. Назовите его OnRowActivated и нажмите Enter. Щелкнув на кнопке Source Code, расположенной ниже окна дизайна формы, вы увидите, что Stetic создал следующий метод:

 protected virtual void OnRowActivated(object o, Gtk.
 RowActivatedArgs args)
 {
 }

Вроде неплохо, но тут вы и подошли к первому недостатку GTK. А именно: получить данные из Tree View гораздо сложнее, чем вы думаете!

Chomp требуется работать с лентами двумя способами. Если мы находимся на вкладке By Feed, то двойной щелчок на одной из лент в Tree View должен загрузить эту самую ленту. Если мы находимся на вкладке By Date, при двойном щелчке на дате в календаре следует загрузить все ленты, но показать новости только за указанную дату. Чтобы как-то унифицировать наш код, создадим метод ReadFeed(), который загружает URL, а затем фильтрует их по дате. При загрузке всех записей данной ленты мы передадим специальный аргумент DateTime.MinValue, обозначающий «игнорировать дату».

Вот код, который надо поместить в OnRowActivated:

 TreeSelection select = tvFeeds.Selection;
 TreeIter iter;
 TreeModel model;
 select.GetSelected(out model, out iter);
 string val = (string)model.GetValue(iter, 0);
 txtFeed.Buffer.Clear();
 ReadFeed(val, DateTime.MinValue);

Первые пять строк иллюстрируют мои слова о занудстве GTK: таким манером мы извлекаем значения из компонента Tree View. Весьма запутанно, но зато годится для любого возможного способа использования Tree View.

Когда выбранное значение попадет в переменную val, мы очищаем буфер Text View и вызываем ReadFeed(), чтобы загрузить и отобразить RSS. Но давайте сначала разберемся с календарем. Для этого найдите сигнал DaySelectedDoubleClick и назначьте ему обработчик DayClicked. В режиме Source Code среды MonoDevelop должен появиться пустой метод DayClicked(), который выглядит так:

 protected virtual void DayClicked(object sender, System.
 EventArgs e)
 {
 }

Этот метод отлавливает двойные щелчки на календаре, а получить выбранную дату из компонента Calendar очень просто.

Но не спешите хвалить GTK: вот я вам сейчас покажу, как перебрать все элементы в Tree View. В DayClicked() надо поместить следующий код:

 txtFeed.Buffer.Clear();
 tvFeeds.Model.Foreach(FeedByDate);

Здесь мы очищаем текстовый буфер для входящих лент, а затем вызываем метод Foreach() модели данных Tree View. Метод FeedByDate() (его я еще не показывал – не гоните коней!) вызывается для каждого элемента в Tree View. А так как метод FeedByDate() вызывается методом Foreach(), он должен соответствовать конкретной функции-прототипу, то есть принимать определенный список параметров. Иначе он работать не будет.

Мы хотим, чтобы функция FeedByDate() читала каждую ленту, которую ей передают, затем посылала ее в ReadFeed() вместе с датой, определенной на компоненте Calendar. Это довольно просто:

 bool FeedByDate(TreeModel model, TreePath path, TreeIter iter)
 {
 string url = (string)model.GetValue(iter, 0);
 ReadFeed(url, new DateTime(calendar.Year, calendar.Month + 1,
 calendar.Day));
 return false;
 }

Первая строка выцарапывает URL ленты из Tree View, после чего он передается методу ReadFeed() вместе с годом, месяцем и днем, выбранными в календаре. Но вы заметили calendar.Month + 1? GTK ведет отсчет месяцев с нуля. Зато дни и года он считает с 1. Я тоже не знаю, почему!

Пару выпусков назад мы рассматривали код, необходимый для чтения RSS-лент. На сей раз мы его используем, но с небольшими изменениями:

  • Вывод будет происходить в текстовый буфер, а не в консоль.
  • GUID’ы кэшироваться не будут – мы хотим, чтобы Chomp загружала все записи каждой ленты, а не записи, которые мы не видели.
  • Если для времени указано DateTime.MinValue, имеется в виду «по дате фильтровать не надо».
  • Иначе, получить дату публикации каждой новости и преобразовать ее в объект DateTime.
  • Затем сравнить каждый объект DateTime с указанной датой и вывести только те записи, которые ей соответствуют.

Тут есть мелкий просчет: одни ленты, включая Linux Format, предоставляют дату публикации в формате Wed, 24 Jan 2007 11:02:43 +0000, а другие вместо ‘+0000’ ставят ‘GMT’. .NET понимает оба формата, но в Mono – возможно, по недосмотру – поддерживается только последний. В качестве обходного пути мы будем обрезать всю подстроку, начиная с символа +.

Время писать код

Вот и код. Если вы читали LXF89, то должны узнать его части!

  protected virtual void ReadFeed(string feed, DateTime filter) {
    XmlDocument doc = new XmlDocument();
    doc.Load(feed);
    TextBuffer text = txtFeed.Buffer;
    XmlNodeList items = doc.SelectNodes(//item”);
    foreach (XmlNode item in items) {
      if (filter == DateTime.MinValue) {
        text.Text += (item.SelectSingleNode(“title”).InnerText) + “\n”;
        text.Text += (“ “ + item.SelectSingleNode(“description”).
 InnerText) + “\n\n”;
      } else {
        string time = item.SelectSingleNode(“pubDate”).InnerText;
        if (time.Contains(+)) time = time.Substring(0, time.
  IndexOf(+));
        DateTime thisdate = DateTime.Parse(time);
        if (filter.Day == thisdate.Day && filter.Month == thisdate.Month
  && filter.Year == thisdate.Year) {
           text.Text += (item.SelectSingleNode(“title”).InnerText) + “\n”;
          text.Text += (“ “ + item.SelectSingleNode(“description”).
 InnerText) + “\n\n”;
        }
      }
    }
  }

Две строки могут вызвать затруднение:

  if (time.Contains(+)) time = time.Substring(0, time.IndexOf(+));
  DateTime thisdate = DateTime.Parse(time);

Первая означает «если строка содержит +, просьба записать в переменную time все от начала строки (от символа с номером ноль) до знака +»: таким образом отсекается часть +0000. Вторая строка означает «возьми строку time и преобразуй ее в тип DateTime'.» Хранить дату как DateTime удобнее, потому что можно работать с днем, месяцем и годом как с числами, а не разбирать строку вручную.

Финишная прямая

Первая версия Chomp почти завершена, но в ней кое-чего не хватает. Двойной щелчок по лентам загружает все ленты. Двойной щелчок по дате загружает все ленты и фильтрует записи по выбранной дате. Но в какой момент мы загружаем ленты в программу? Ответ: а ни в какой!

Наш предыдущий XML-считыватель тоже загружал список лент, поэтому можно просто взять и модифицировать его для нашего интерфейса на GTK. А именно, давайте поместим каждый URL в Tree View, чтобы пользователи могли нажимать на них. Здесь опять проявится занудство GTK, поэтому возьмите себя в руки:

  string[] sitelist;
  if (File.Exists(“sitelist.txt)) {
    sitelist = File.ReadAllLines(“sitelist.txt);
  } else {
    sitelist = new string[0];
  }
  // это значит, что мы хотим сохранить строку в строке Tree View, но в
 действительности нам нужен только URL!
 TreeStore store = new TreeStore (typeof(string), typeof(string));
 foreach(string site in sitelist) {
   // “Foo” это место размещения
   store.AppendValues(site, “Foo”);
 }
 tvFeeds.Model = store;
 tvFeeds.AppendColumn(“URLs”, new CellRendererText(), “text”,0);

Чтобы показать данные, Tree View нужен по крайней мере один столбец. Смело отключайте заголовки в свойствах компонента Stetic, в них нет нужды. Теперь создайте файл sitelist.txt в вашем рабочем каталоге программы (наподобие /путь/до/проекта/bin/Debug), заполните его URL-адресами лент, и пусть поработает!

Весь код этого учебника, даже с некоторыми дополнительными возможностями (см. врезку Chomp 0.2), включен на диск. Но чтобы проект созрел для помещения на SourceForge, не мешает его усовершенствовать. Оставляю Chomp в ваших умелых руках... LXF

Chomp 0.2

Версия Chomp на диске базируется на том, что мы сделали, и позволяет пользователям временно добавлять новые ленты, но вы можете сделать еще кое-что:

  • Заставить работать функцию удаления ленты.
  • Кэшировать ленты и GUID, чтобы при отображении они загружались из памяти, а не скачивались каждый раз из Сети.
  • Реализовать поиск в кэшированных лентах.
  • Добавить Refresh и RefreshAll.
  • Поместить что-нибудь в строку состояния!
  • Заполнить меню осмысленными действиями.
  • Добавлять пользовательские ленты в файл sitelist.txt.

Целью этого урока было научить вас использовать Stetic, и я думаю, мы справились неплохо. Дальнейшая работа будет в основном писа ниной кода: обвешаем интерфейс всякими крутыми штуками. Почаще сохраняйте результат, и все будет в порядке – резвитесь!

Персональные инструменты
купить
подписаться
Яндекс.Метрика