- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
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 и Glade очень похожи: оба поддерживают drag&drop для создания пользовательских интерфейсов на GTK. Но Stetic интегрирован в MonoDevelop, то есть может генерировать код, связывая виджеты с переменными. При желании можно использовать в Mono и Glade, но рекомендуется все-таки Stetic.
Первым делом нарисуем наш интерфейс с помощью Stetic. Никакой функциональности это не даст, зато даст представление о том, как будет выглядеть готовое приложение, а я всегда считал, что это позволяет шагнуть далеко вперед. Другая причина, почему стоит сначала спроектировать интерфейс – вы сразу же увидите, будет ли ваш проект неуклюж или невразумителен, и, в случае чего, заранее примете меры по редизайну интерфейса. Помните, что у лучших проектов интерфейс пользователя разрабатывается очень тщательно – не зацикливайтесь исключительно на написании кода в надежде, что GUI сложится само собой!
Прежде чем приступать, полностью обновите версию Fedora, потому что Stetic – сравнительно молодой инструмент, и лучше использовать свежую версию MonoDevelop для минимизации числа возможных отказов. Да почаще сохраняйте результаты, поскольку Stetic не страдает излишней стабильностью.
Итак, аккуратно выполните инструкции по работе в MonoDevelop, приведенные далее и продолжите чтение.
Часть 1 Создание интерфейса пользователя
1 Создайте новый проект Выберите проект С# из области шаблонов, а затем – Gtk# 2.0 Project, справа. Назовите его chomp, снимите галочку с Create Separate Solution Subdirectory и нажмите New.
2 Откройте MainWindow Откройте дерево пользовательского интерфейса в панели Solution и дважды щелкните на MainWindow. Откроется MainWindow.cs в режиме Designer.
3 Добавьте четыре области В палитре Widgets пройдите до Containers и перетащите VBox на окно. Щелкните на нем правой кнопкой и выберите Vbox1 > Insert Before для четырех областей.
4 Преобразуйте области Перетащите главное меню, панель инструментов, Hpaned и строку состояния во все четыре области, начиная с верхней. Разделите Hpaned на две примерно равные части, как показано на рисунке.
5 Новый Notebook Перетащите VBox в левую часть Hpaned. Нажмите на ней и выберите Delete. Перетащите Notebook и Button на верхнюю и нижнюю стороны VBox.
6 TreeView и Calendar Перетащите Tree View на вкладку Page1 компонента Notebook. Щелкните на нем правой кнопкой, выберите Notebook1, затем Insert Page After. На вкладке Page2 вставьте Calendar.
7 Добавьте полосы прокрутки В большом пространстве справа от элемента Hpaned вставьте Scrolled Window, затем поместите в него Text View. Пользователи смогут прокручивать текст.
8 Организуйте меню Дважды щелкните на пустом меню, наберите /‘File’, затем нажмите Enter. Таким образом, вы создадите подменю – наберите в нем ‘Quit’. Создайте меню Help с подменю About.
9 Кнопки панели инструментов Дважды щелкните по панели инструментов, выберите Select Icon, затем gtk-new. Добавьте две другие кнопки: одну с gtk-refresh, другую с gtk-delete.
Часть 2 Реализуем функционал программы
Создание интерфейса позади, но для готового GUI еще кое-чего нехватает. Нужно сделать три вещи. Во-первых, назначить нормальные имена виджетам: пока что они называются button1, textview2 и т.д. Во-вторых, связать виджеты с полями: тогда мы сможем обращаться к ним из кода на C#. И в-третьих, изменить свойства виджетов, чтобы они выглядели как надо и делали то, что мы хотим.
Проверьте, что файл sitelist.txt содержит адреса URL лент, по одному на строке, и что он находится там же, где и файл chomp.exe. Обычно это каталог bin/Debug или bin/Release, если вы сделали релиз-версию.
Имя виджета – это просто одно из свойств, поэтому задачи 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 – он даже создаст за нас базовый метод!
GTK предусматривает масштабирование компонентов интерфейса при изменении размера окна, а также их сжатие и растяжение при переводе надписей на другие языки, смене шрифта и других параметров среды выполнения. Вот почему мы используем VBox' и HBox, а не размещаем компоненты вручную.
Прежде всего обработаем событие, возникающее, когда пользователь выделяет что-то в 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 – возможно, по недосмотру – поддерживается только последний. В качестве обходного пути мы будем обрезать всю подстроку, начиная с символа +.
Время писать код
Когда вы будете читать этот материал, уже, возможно, выйдет MonoDevelop версии 1.0, хотя бы в виде бета-версии. Stetic – одна из активно разрабатываемых и быстро меняющихся областей, поэтому в новом релизе будут исправлены многие ошибки и даже добавлены новые... возможности. Стоит проверить!
Вот и код. Если вы читали 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, и я думаю, мы справились неплохо. Дальнейшая работа будет в основном писа ниной кода: обвешаем интерфейс всякими крутыми штуками. Почаще сохраняйте результат, и все будет в порядке – резвитесь!
Категории: Учебники | Mono | Пол Хадсон