Написание GUI-приложений на Perl ничем не отличается от написания на других динамических языках. Все зависит от качества оберток над оригинальными библиотеками и их своевременного обновления.
Почему wxWidgets ?
wxWidgets является по-настоящему кросс-платформенным GUI-инструментарием. В отличие от, например, Qt , который тоже вроде бы является кросс-платформенным, wxWidget на запускаемых платформах использует родные библиотеки, а не пытается их эмулировать. Таким образом приложения выглядят не cовсем одинаково, но совершенно привычно для своей графической оболочки.
Более того, поддержка у wxWidgets для Perl является полной, качественной и современной. Например, популярный редактор Padre тоже написан с помощью wxWidgets. Имея под рукой большой открытый и свободный проект, всегда можно подглядеть, как реализуются те или иные виджеты, можно на конкретном примере быстро научиться.
Настройка окружения
Для разработки я использую платформу GNU/Linux. Поэтому дальше приведена установка wxWidgets именно для моей ОС. Проще всего устанавливать библиотеки из дистрибутивных пакетов, на примере Debian:
wxWidgets C++ GUI | how-to | getting started | tutorial | guide
# apt-get install libwx-perl
Конечно, всегда можно установить и с CPAN, но тогда придется много компилировать и можно наткнуться на несовместимость версий.
$ cpanm Wx
Первое приложение
Стандартным первым приложением будет окно на котором написано “Hello, world!”:
use strict; use warnings; use Wx; my $app = Wx::SimpleApp->new; my $frame = Wx::Frame->new(undef, -1, «Hello, world!»); $frame->Show; $app->MainLoop;
Wx::Frame это окно, у которого может быть изменен размер, у него может быть статусная строка, а также меню. Т.е. это окно общего назначения. Кроме Wx::Frame существует также Wx::Dialog и другие. Для добавления визуальных элементов можно воспользоваться различными классами и методами wxWidgets . Например, чтобы добавить кнопку, которая закрывает приложение, напишем следующий код:
use strict; use warnings; use Wx; use Wx::Event; my $app = Wx::SimpleApp->new; my $frame = Wx::Frame->new(undef, -1, «Hello, world!»); my $button = Wx::Button->new($frame, -1, ‘Close’); Wx::Event::EVT_BUTTON($frame, $button, sub < $frame->Close(1) >); $frame->Show; $app->MainLoop;
Все методы, события и т.д. хорошо описаны в документации wxWidgets. Не будем на этом подробно останавливаться. Рассмотрим общий подход в написании GUI-приложений.
Генераторы XML-интерфейса
Для небольшого приложения это вполне допустимо, также как и для простого Perl-скрипта можно все записать в один файл. Однако это очень неудобно для больших приложений с большим количеством окон, диалогов, сложной логикой.
Для графических приложений применяют тот же подход, что и для веб-приложений — MVC, или разделение бизнес-логики, отображения и управления. Так же можно поступить и с GUI-программой.
Для построения интерфейса лучше воспользоваться специальными программами, генерирующими XML-настройки (XRC-файлы), которые, будучи загруженными в приложении, преобразуются в графический интерфейс. Это гораздо легче поддерживать и проще изменять.
C++ GUI Programming For Beginners | Episode 1 — Installing wxWidgets
Для wxWidgets существует несколько таких генераторов. Наиболее полным является wxFormBuilder . Он позволяет создавать всевозможные элементы, указывать их расположение на окнах и т.д.
Например, XRC-файл для предыдущего приложения может выглядеть так:
wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL 500,300 1 0 Close 0
Теперь загрузим эту конфигурацию в приложении:
use strict; use warnings; use Wx; use Wx::XRC; use Wx::Event; my $app = Wx::SimpleApp->new; my $xrc = Wx::XmlResource->new(); $xrc->InitAllHandlers(); $xrc->Load(‘example.xrc’); my $frame = Wx::Frame->new; $xrc->LoadFrame($frame, undef, ‘MainFrame’); Wx::Event::EVT_BUTTON( $frame, Wx::XmlResource::GetXRCID(‘CloseButton’), sub < $frame->Close(1) > ); $frame->Show; $app->MainLoop;
Для того, чтобы достать объект Wx::Frame , используем метод LoadFrame :
my $frame = Wx::Frame->new; $xrc->LoadFrame($frame, undef, ‘MainFrame’);
А для того, чтобы достать объект кнопки, вначале получаем ее id по имени:
Wx::XmlResource::GetXRCID(‘CloseButton’)
А затем привязываем событие.
Всегда стоит понятным образом называть все элементы, а не оставлять что-то вроде m_Button_1 , это сделает управляющий код существенно понятнее.
Разделение на классы
Как и любое Perl-приложение, разделение на отдельные пакеты и классы упрощает понимание кода, делает возможным тестирование отдельных классов, а также позволяет легко наследовать и менять поведение графических элементов.
Разделим наше приложение на несколько составляющих.
Основной класс приложения
package MyApp; use strict; use warnings; use base ‘Wx::SimpleApp’; use Wx; use Wx::XRC; use MyMainFrame; sub OnInit < my $self = shift; my $xrc = Wx::XmlResource->new(); $xrc->InitAllHandlers(); $xrc->Load(‘app.xrc’); my $frame = MyMainFrame->new; $xrc->LoadFrame($frame, undef, ‘MainFrame’); $frame->Show; > 1;
Задача основого класса выполнить загрузку настроек, создать основное окно и показать его.
Класс основного окна
Задача этого класса выполнить настройку своего окна, т.е. прописать все события, в данном случае обработать нажатие на кнопку CloseButton .
Скрипт запуска
Скрипт запуска app.pl является входной точкой для запуска приложения.
#!/usr/bin/env perl use strict; use warnings; use Wx; use MyApp; MyApp->new->MainLoop;
С помощью такой декомпозиции каждый класс занимается своим делом, а структура приложения выглядит следующим образом:
app.pl app.xrc lib/ MyApp.pm MyMainFrame.pm
Можно у каждого класса сделать какой-то уникальный префикс, например GUI, чтобы их отличать от обычных Perl-классов, которые будут заниматься логикой приложения.
Генераторы Perl-кода
wxFormBuilder , как было уже указано, может генерировать XML-представление, однако он позволяет гененировать и код. К сожалению, из коробки нет поддержки генерации Perl-кода. Однако, разработчики Padre реализовали собственный генератор Perl-кода из файла проекта wxFormBuilder. Для этого потребуются два модуля: FBP и FBP::Perl . Написав простейший скрипт для создания классов, получим на выходе дерево файлов, где каждый — это отдельное окно:
Плюс этого подхода еще и в том, что нет необходимости вручную привязывать сигналы к элементам. Указав в wxFormBuilder название обработчика, PBP::Perl правильно сгенерирует соответствующий код. Вот как, например, выглядит класс основной формы:
Как же использовать эти классы? Добавлять в них функционал не стоит, потому как они автоматически генерируются из файла проекта. Наиболее правильным решением будет унаследоваться от сгененированного класса и там уже имплементировать нужный код. Кроме того, имеет смысл генерировать классы с определенным префиксом, чтобы было понятно, какие классы автоматические, а какие написаны вручную.
Вот так выглядит реализация обработчика события OnClick в дочернем классе:
package MyApp::MainFrame; use strict; use warnings; use base ‘MyApp::FBP::MainFrame’; sub OnClick < my $self = shift; $self->Close(1); > 1;
Проект с использованием сгенерированных классов выглядит следующим образом:
app.pl lib/ MyApp/ FBP/ MainFrame.pm MainFrame.pm MyApp.pm generate.pl simple.fbp
simple.fbp это файл проекта wxFormBuilder, а generate.pl — скрипт для генерации.
Модуль MyApp.pm придется тоже написать вручную, но его код предельно прост:
package MyApp; use strict; use warnings; use base ‘Wx::SimpleApp’; use MyApp::MainFrame; sub OnInit < my $self = shift; my $frame = MyApp::MainFrame->new; $frame->Show; > 1;
А app.pl остается тем же, что и в случае использования XML-представления.
Разделение логики
Как уже упоминалось, при написании графических приложений разделяется собственно представление и логика самого приложения. Идеально, если приложение будет реализовано таким образом, что замена графической библиотеки другой, или же веб-приложением, или консольным вариантом не повлечет каких-либо или, как минимим, никаких серьезных изменений.
Например, если в приложении необходимо сделать запрос к веб-сайту для получения каких-то данных, стоит выделить это в отдельный класс, не зависящий от графической библиотеки. Это позволит гораздо проще его протестировать стандартными средствами. В случае тестирования графических программ это сделать гораздо сложнее.
Впрочем, тестировать графические интерфейсы тоже можно и нужно, как например, с помощью Selenium тестируют и веб-приложения. Для X11 есть набор инструментов Xnee, который позволяет записывать сценарии, а затем выполнять их в автоматическом режиме.
Блокировки
Если писать серьезные приложения на wxWidgets , в скором времени вы столкнетесь с тем, что при выполнении требующих времени задач активное окно блокируется и как будто замирает. Все дело в блокировке. Во время выполнения вашего кода wxWidgets ждет, пока он закончится.
Стандартным способом решения этой проблемы является использование тредов (потоков, нитей), когда задачи, которые необходимо выполнить на заднем фоне, выполняются в отдельном потоке. В этом случае окно не блокируется, а после завершения задачи основное окно получает ответ и обрабатывает его.
Кроме потоков возможно использование асинхронных фреймворков по типу POE . На Linux возможно использование wxGTK , а вместе с ним AnyEvent . В этом случае придется писать событийно-ориентированный код со всеми преимуществами и недостатками последнего.
Также возможно выполнение фоновых задач в отдельном процессе с помощью wxExecute , но это больше подходит для запуска сторонних приложений.
Выполнение задач в отдельном потоке
Самый распространенный и простой способ выполнить задачу в фоновом режиме — использовать потоки. Чтобы использовать потоки с Wx , необходимо, во-первых, убедиться, что perl собран с поддержкой threads :
perl -V | grep threads
А во-вторых, подключить прагму threads ДО подключения Wx (это стоит сделать также в скрипте запуска приложения):
use threads; use Wx;
Например, при нажатии на кнопку, мы хотим выполнить какой-то код и оповестить главное приложение о результате. Скелет такого приложения будет выглядеть следующим образом:
Переменные с тегом shared необходимы для обмена данными между потоками, так как потоки выполняются одновременно, и использование обычных переменных небезопасно. Wx сам проследит, что переменные объявлены как shared , обезопасив таким образом программиста.
В этом приложении при нажатии на кнопку регистрируется обработчик завершения события, далее создается поток для выполнения работы. Когда поток завершается, он сигнализирует главному приложению о своих результатах. Главное приложение в обработчике, зарегистрированном ранее, получает данные и производит какие-то действия для отображения результата.
Проектируя код таким образом, т.е. исполняя код в фоновом режиме, можно писать обычные Perl-модули совершенно независимо от графической библиотеки.
Пример приложения
Для примера, напишем приложение, которое получает от MetaCPAN десять последних загруженных модулей и отображает их в виде списка.
Модель
Вначале реализуем модель, т.е. основу приложения. В нашем случае это модуль, который будет получать последние добавленные дистрибутивы. Он может выглядеть следующим образом:
Более подробно о том, как работать с API MetaCPAN, можно почитать в документации. Там же есть множество примеров.
Отображение
Графическое приложение будет представлять собой одно единственное окно с кнопкой Fetch и списком. При нажатии на кнопку в отдельном потоке создаем объект класса ModulesFetcher и возвращаем результат. Главное графическое приложение обрабатывает результат и заполняет список.
Скелет всего приложения уже описывался нами выше, поэтому здесь покажем только пример использования стороннего модуля:
В методе work вызывается метод fetch на объекте модели, затем результат преобразуется в shared -переменную, и создается сигнал главному окну с передачей результата.
В методе done запрашиваем полученные данные у события с помощью метода GetData и затем в цикле добавляем их в объект списка. Перед этим не забываем очистить старые данные.
Выводы
Писать графические приложения на Perl можно вполне успешно. С помощью библиотеки wxWidgets это еще и просто. Правильно разделяя модель и отображение, можно добиться переносимости между различными графическими библиотеками, а также упростить реализацию.
Если читателям интересны подобные статьи, в планах есть написание аналогичного примера с помощью библиотеки Gtk . Жду ваших комментариев.
Показать комментарии
Источник: pragmaticperl.com
Перевод книги Julian’а Smart’а – Глава II – Начало
Я буду очень признателен, если вы будете сообщать обо всех орфографических и пунктуационных ошибках, которые встретите в тексте. Также желательно сообщать о всех не совсем ясно написанных частях текста.
Начало
В этой главе рассматривается структура простой программы, написанной с использованием wxWidgets.
Мы покажем запуск и процесс завершения wxWidgets-приложения, как показать главное окно и как обрабатывать команды от пользователя. На этом, следуя философии wxWidgets использовать только простые и красивые решения, мы главу и закончим. Для компиляции примеров вам возможно придется обратится к Приложению 1 Установка wxWidgets.
Небольшой пример приложения на базе wxWidgets
Рис. 2.1 показывает, как наше приложение будет выглядеть в операционной системе Windows.Минимальное приложение на wxWidgets показывает главное окно (класс wxFrame) со строками меню и статуса. Меню позволяет увидеть вам информацию О программе или выйти из программы. Это очень простое приложение, однако его вполне достаточно, чтобы проиллюстрировать некоторые базовые принципы построения приложений. Кроме того с накоплением опыта вы можете использовать этот пример, добавляя в него необходимую вам функциональность.
Класс приложения
Каждое приложение на wxWidgets определяет свой собственный класс приложения, являющийся потомком от wxApp. В программе существует единственный экземпляр данного класса, который представляет из себя представление данного приложения. Обычно этот класс объявляет функцию OnInit, которая вызывается, когда библиотека wxWidgets готова запустить ваш код (функция является эквивалентом функции main языка C или WinMain в Win32-приложениях).Ниже дано минимально возможное объявление класса приложения:
// Объявляем класс приложения class MyApp : public wxApp < public: // Вызывается при старте приложения virtual bool OnInit(); >;
Реализация функции OnInit обычно создает по крайней мере одно окно, считывает параметры из командной строки, инициализирует нужные для работы структуры данных и выполняет другие действия, необходимые для запуска программы. Если функция возвращает true, то wxWidgets запускает петлю сообщений, которая обрабатывает ввод пользователя и запускает обработчик сообщений в случае необходимости. Если данная функция возвращает false, то wxWidgets корректно очищает свои внутренние структуры и завершает работу приложения.Простейшая реализация функции OnInit может иметь следующий вид:
// Инициализируем приложение bool MyApp::OnInit() < // Создаем главное окно приложения MyFrame *frame = new MyFrame(wxT(«Minimal wxWidgets App»)); // Показываем его frame->Show(true); // Запускаем петлю сообщений return true; >
// Говорит wxWidgets, что надо создать объект MyApp IMPLEMENT_APP(MyApp)
Без этого определения wxWidgets не сможет создать объект вашего приложения.
Данный макрос также вставляет код, который проверяет, что объект является приложением и библиотека была скомпилирована с необходимыми опциями. Это позволяет wxWidgets сообщать о некоторых ошибках, которые впоследствии могли бы вызвать появление трудно выявляемых ошибок времени выполнения.Когда wxWidgets создает объект типа MyApp, результат сохраняется в глобальной переменной wxTheApp. Можно использовать эту переменную в вашей программе, однако лучше явно не обращаться к ней в своем коде. Вместо этого, после объявления вашего класса приложения поместите макрос
// Реализация MyApp public: //Конструктор MyFrame(const wxString // Обработчики сообщений void OnQuit(wxCommandEvent void OnAbout(wxCommandEvent private: // Этот класс перехватывает сообщения DECLARE_EVENT_TABLE() >;
Наш класс для фрейма содержит конструктор, два обработчика сообщений (связывающих пункты меню с C++ кодом) и макрос, который сообщает, что класс содержит обработчики сообщений.
Обработчики сообщений
Как вы уже знаете, функции обработки сообщений в MyFrame не являются виртуальными и не должны быть виртуальными. Тогда как же они вызываются? Ответ расположен в следующей таблице сообщений:
// Таблица сообщений для MyFrame BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(wxID_ABOUT, MyFrame::OnAbout) EVT_MENU(wxID_EXIT, MyFrame::OnQuit) END_EVENT_TABLE()
Таблица сообщений, помещенная в файл с реализацией класса, говорит wxWidgets каким образом связываются сообщения, поступающие от пользователя, и методы класса.В указанной выше таблице нажатие кнопки мыши на пунктах меню с идентификаторами wxID_EXIT и wxID_ABOUT направляется функциям MyFrame::OnQuit и MyFrame::OnAbout соответственно. Макрос EVT_MENU является одним из многих возможных макросов таблицы сообщений, которые вы можете использовать, чтобы указать wxWidgets какие типы сообщений направлять в функцию. Идентификаторы, используемые в нашем примере, являются предопределенными, но чаще всего вы должны вводить свои собственные с помощью перечислений (enum), констант или директив препроцессора (#define).Этот тип таблицы сообщений называется статическим, то есть не может быть изменен в процессе выполнения программы. В следующей главе мы расскажем, как создавать динамические обработчики сообщений.Пока мы разбираемся с таблицами сообщений, давайте посмотрим на две функции, которые у насиспользуются для обработки сообщений.
void MyFrame::OnAbout(wxCommandEvent wxString msg; msg.Printf(wxT(«Hello and welcome to %s»), wxVERSION_STRING); wxMessageBox(msg, wxT(«About Minimal»), wxOK | wxICON_INFORMATION, this); >void MyFrame::OnQuit(wxCommandEvent // Уничтожаем фрейм Close(); >
Метод MyFrame::OnAbout показывает небольшое сообщение пользователю, когда тот выбирает из меню пункт About.
Функция wxMessageBox принимает в качестве аргументов текст сообщения, заголовок окна, комбинацию стилей и указатель на родительское окно.Как это указано в таблице сообщений, функция MyFrame::OnQuit вызывается, когда пользователь выбирает в меню пункт Quit. Наш обработчик данного сообщения вызывает метод Closeдля уничтожения фрейма и таким образом завершает работу приложения, так как программасостоит всего из одного фрейма.
На самом деле метод Close не уничтожает приложение, он просто генерирует сообщение wxEVT_CLOSE_WINDOW, обработчик которого по умолчанию уничтожает фрейм, используя для этого метод wxWindow::Destroy.Существуют другие пути для завершения работы приложения — пользователь может щелкнуть на кнопке закрытия, расположенной на фрейме, или выбрать пункт Close из системного меню (или меню оконного менеджера среды запуска). Когда метод OnQuit вызывается в этихслучаях?
Ну если честно, то он не вызывается. В указанных случаях wxWidgets посылает фрейму сообщение wxEVT_CLOSE_WINDOW через вызов функции Close (подобно тому, как это делаем мы в методе OnQuit). wxWidgets по умолчанию перехватывает это сообщение и уничтожает окно. Ваше приложение может переопределить данное поведение и, например, запрашивать подтверждение у пользователя перед закрытием. Обратитесь к Главе 4 Окна за дополнительной информацией.В нашем примере этого делать не требовалось, но большая часть приложений реализует функцию OnExit в классе приложения для корректной очистки структур данных перед выходом. Обратите внимание, что эта функция вызывается только если OnInit возвратила true.
Создание фрейма
Наконец, у нас есть конструктор для фрейма, который инициализирует иконку для приложения, меню и строку состояния.
#include «mondrian.xpm» MyFrame::MyFrame(const wxString // Устанавливаем иконку для фрейма SetIcon(wxIcon(mondrian_xpm)); // Создаем меню wxMenu *fileMenu = new wxMenu; // Добавляем пункт «About» (о приложении), который // должен показывать маленькую помощь wxMenu *helpMenu = new wxMenu; helpMenu->Append(wxID_ABOUT, wxT(«), wxT(«Show about dialog»)); fileMenu->Append(wxID_EXIT, wxT(«E), wxT(«Quit this program»)); // Теперь добавляем созданное меню в строку меню. wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(fileMenu, wxT(«)); menuBar->Append(helpMenu, wxT(«)); // . и присоединяем к фрейму SetMenuBar(menuBar); // Создаем строку состояния для красоты CreateStatusBar(2); SetStatusText(wxT(«Welcome to wxWidgets!»)); >
Первая программа на wxWidgets
От нечего делать решил написать маленький тьюториал. В своё время меня по-настоящему достало отсутствие нормальных обучалок по wxWidgets.
Прежде всего, обговорю два момента. Во-первых, я надеюсь,что мой читатель знает,что такое библиотека wxWidgets, что он умеет программировать на C++, что он уже установил себе всё необходимое и настроил компилятор (самый простой способ на Windows — это поставить IDE wxDev-Cpp, на Linux все способы одинаково простые) и что он горит желанием начать.
Второе — это то,что я хочу сейчас сделать. Я хочу создать минимальное рабочее GUI-приложение на wxWidgets, не обращая пристального внимания на оптимизации и трюки. Из чего состоит минимальное приложение? Дайте подумать…кусочек текста на сером фоне подойдёт.
Сперва даю код,после этого — объясняю,что он делает, договорились?
За этими 25 строчками кроется глубинный смысл. Пока что скомпилируйте, а я расскажу,что да как. Напоминаю,что на Linux это делается командой:
g++ wx-config —cxxflags wx-config —libs -o
Начнём,пожалуй. Строка 1 подключает собственно wxWidgets. Для случая прекомпиляции предусмотрен иной подход, но что я говорил насчет оптимизаций?
Итак, на строке 3 мы создаём класс MainApp. Это будет основной класс нашей программы,о чём напоминают строчки 14 и 15.В строках 8-12 создаётся фрейм программы — собственно окошечко, в котором будет виден текст. Позднее туда ещё добавится пара контролов.
Если вы заметили,то программа не содержит функции main(). Вместо неё первой выполняется функция OnInit(),это строки 16-21. В них всё довольно топорно: пичкаем фрейм нужными параметрами (заголовок, позиция на экране и размер), выводим на экран, и даём команду сделать его основным окном программы.
В строках 23-25 описывается содержимое фрейма. Собственно, что и будет выведено после выполнения строки 18. У нас это безымянный текстовый блок. немного о параметрах конструктора:
new wxStaticText (this, wxID_ANY, _T(“Я написал что-то на wxWidgets!!”), wxPoint(10,100), wxDefaultSize,0,wxStaticTextNameStr);
this — это указатель на окно,в котором выводится текстовый блок. У нас, собственно,и нет выбора.
Значение wxID_ANY нужно объяснять долго. wxWidgets использует систему ID — то есть,каждый объект на фрейме имеет собственный целочисленный номер. Это понадобится чуть позже, когда придётся обрабатывать события. Пока что обработка событий в наши планы не входит, поэтому и ID мы задаём произвольным образом.
wxPoint(10,100) — это положение тестового блока на фрейме. Объект класса wxPoint задаёт его левый верхний угол — здесь только две координаты, которые считаются от левого верхнего угла.Вместо этого можно написать wxDefaultPosition — размещение по умолчанию, но ведь лёгкими путями не достигнуть просветления.
Здесь хотелось бы сделать замечание. При ручной расстановке я заметил,что в разных осях разные шрифты и размеры блоков. Так, в одной операционке мне не хватает ширины на фрейме,в другой — высоты. Разница в шрифтах, разница в размере границ полей..всё это даёт сильные перекосы, поэтому старайтесь придерживаться всё-таки стандартных компоновщиков,это сильно сэкономит нервы и время.
wxDefaultSize — размер блока. Рекомендую не вычислять вручную, библиотека это превосходно сделает за вас.
0 — отвечает за стиль текста. Если интересно — смотреть здесь. Но в большинстве случаев вам это смотреть не понадобится.
wxStaticTextNameStr — имя окна. Играть с этим не требуется,но если есть желание — почитайте маны и попробуйте.
Итак, вроде всё. У нас получилось одинокое окошко с текстовым блоком.Попробуем чуть-чуть усложнить задачу. За что я не люблю остальные руководства — они думают, что статусбар и меню — это обязательные элементы интерфейса. Чушь собачья! Интерфейс должен быть максимально простым и требовать не больше одного клика мышки.Что требует всего лишь одного нажатия?
Кнопка.
Так, на сегодня пока всё. В следующий раз попробуем сделать ещё одну бессмысленную программу, в которой значение текстового поля будет менят ься с щелчком кнопки. При желании, кнопку можно будет сделать красной 🙂
Источник: oreolek.me