Создание кросс-платформенных GUI-приложений с использованием wxWidgets

Ноэл Рэппин, старший инженер-программист, Motorola, Inc.

Набор инструментальных средств wxWidgets содержит мощные кросс-платформенные средства разработки графического пользовательского интерфейса (GUI). Не только "родной" C++, но и другие языки программирования предлагают средства для использования данного инструментального набора. Узнайте, как использовать wxWidgets для создания элегантных и очень полезных GUI-приложений на предпочитаемом вами языке программирования.

Почему нужно использовать wxWidgets? Потому что вы хотите быстро и просто написать GUI-приложение, которое работает на разных платформах. Вы также хотите использовать предпочитаемый вами язык программирования и желаете, чтобы ваш GUI выглядел так же хорошо, как GUI на следующем рисунке:

Рисунок 1. Почтовый клиент Chandler
Рисунок 1. Почтовый клиент Chandler

На рисунке 1 показана программа Chandler, предназначенная для управления электронной почтой и календарем и разрабатываемая организацией Open Source Application Foundation. Она пишется с использованием инструментального набора wxWidgets. Хотя оригинальная версия wxWidgets реализована на C++, создатели Chandler используют Python с набором инструментальных средств wxPython, выступающим в качестве "оболочки", позволяющей Python-коду взаимодействовать с библиотекой C++. wxWidgets использует, по возможности, "родные" объекты; эти объекты дополняются (там, где необходимо) мощными пользовательскими виджетами. Вы можете написать wxWidgets-программу, которая будет работать на широком многообразии платформ, а также можете использовать различные языки программирования для этого.

Начало работы с wxWidgets

В данной статье я предполагаю, что вы уже посетили домашнюю страницу wxWidgets и загрузили нужный пакет для вашей платформы. Я также предполагаю, что вы выполнили на вашем компьютере команды или настройки по интеграции библиотеки wxWidgets с выбранным вами компилятором или интегрированной средой разработки (integrated development environment - IDE). После всего этого вы можете приступить к кодированию.

Основными компонентами wxWidgets-программы являются два главных объекта: объект-приложение и объект-фрейм. Вы, конечно же, можете иметь более одного фрейма. Кроме того, в вашем коде вам понадобится разместить несколько wxWidgets-макросов. Рассмотрим, как скомпоновать эти части.

Ссылка на библиотеку wxWidgets

Чтобы ссылаться на библиотеку wxWidgets, вы должны включить ее в код. Поместите следующую строку в верхней части ваших заголовочных (header) файлов:

#include "wx/wx.h"

Заголовочный файл wx/wx.h содержит все wxWidgets-определения, которые вам могут понадобиться. Если вы очень беспокоитесь о производительности, можете заменить этот файл операторами include с конкретными заголовочными файлами, которые собираетесь использовать.

Определение класса приложения

Далее вы должны определить класс приложения. В большинстве простых случаев этот класс делает не так уж много, но у вас он должен быть. wxWidgets-приложение наследуется из класса wxApp, и минимальное определение выглядит так:

class DemoApp : public wxApp {
public:
  virtual bool OnInit();
}

Функция OnInit() вызывается при запуске вашего приложения - практически, это ваш метод main().

После определения класса приложения поместите куда-нибудь в ваш код следующий макрос:

IMPLEMENT_APP(DemoApp)

Вы должны заменить DemoApp на имя вашего класса приложения. Этот макрос создает реальный метод main(), который wxWidgets использует. Он также создает экземпляр объекта приложения и начинает процесс инициализации.

Определение класса фрейма

Теперь определите класс фрейма, который будет представлять главное окно вашего приложения. Родительским классом wxWidgets является wxFrame. В листинге 1 приведен короткий пример.

Листинг 1. Пример класса wxFrame

				
class DemoFrame : public wxFrame {
public:
   DemoFrame(const wxString& title);
   void OnButtonPress(wxCommandEvent& event);

private:
   DECLARE_EVENT_TABLE()
};

Рассмотрим имена, которые вам могут быть не знакомы. wxString - это специализированный wxWidgets-класс, являющийся оболочкой над string и использующийся для операций со строками в инструментальном наборе wxWidgets. wxWidgets не использует классы Standard Template Library (STL), для того чтобы не ограничивать использование wxWidgets платформами, на которых доступна STL (если захотите, можете указать ключ командной строки во время компиляции для использования STL). Аналогично, wxCommandEvent - один из родительских классов для событий, в частности, управляющих (command) событий, которые представляют собой высокоуровневые события, обычно относящиеся к действиям пользователя, например, нажатие кнопки или выбор из списка. Наконец, макрос DECLARE_EVENT_TABLE нужен любому wxWidgets-объекту, желающему реагировать на события. Этот макрос в данной статье реализует небольшой демонстрационный фрейм.

Определение таблицы событий

Для реального реагирования на события вы должны определить таблицу событий внутри ваших файлов. Это еще один макрос, и он (в нашем случае) выглядит так, как показано в листинге 2.

Листинг 2. Макрос таблицы событий

				
BEGIN_EVENT_TABLE(DemoFrame, wxFrame)
   EVT_BUTTON(wxID_CLOSE,  DemoFrame::OnButtonPress)
END_EVENT_TABLE()

Макрос BEGIN_EVENT_TABLE() принимает два аргумента: класс, для которого предназначена таблица событий, и прямой предок этого класса. Макрос END_EVENT_TABLE, как и ожидается, обозначает конец таблицы событий. Между этими макросами вы можете указать любое число макросов для конкретных событий. В примере данной статьи используется только один.

Инструментальный набор wxWidgets имеет несколько различных макросов событий, каждый из которых соответствует различным типам событий. В данном случае EVT_BUTTON соответствует нажатию на кнопку. Первым параметром макроса является идентификатор, указывающий конкретную кнопку, которая будет обрабатываться. Идентификатор wxID_CLOSE является одним из нескольких предопределенных идентификаторов, связанных с обычными функциональными возможностями приложения. Предопределенный идентификатор здесь используется исключительно для удобства, хотя в некоторых случаях используются специализированные идентификаторы, которые активизируют специальную обработку системой wxWidgets. Вторым параметром макроса является полностью квалифицированное имя метода, который должен быть вызван при активизации события. Все события в wxWidgets управляются с использованием макросов событий, аналогичных описанному.

Определение ваших методов

Теперь наступило время начать определять некоторые методы. Я показываю три простых метода, начиная с метода OnInit(), приведенного в листинге 3.

Листинг 3. Метод OnInit()

				
bool DemoApp::OnInit() {
   DemoFrame *frame = new DemoFrame("DeveloperWorks Demo");
   frame->Show(true);
   return true;
}

Первые две строки метода делают то, что вы и ожидали бы при старте GUI-программы - они создают и показывают главное окно. Однако третья строка, на самом деле, является самой важной для приложения. Возврат значения true указывает механизму wxWidgets на то, что инициализация прошла успешно, и программа может продолжать свою работу. Возврат значения false, наоборот, остановил бы приложение и вызвал бы выход из него.

Метод OnInit() ссылается на конструктор DemoFrame. В этом конструкторе вы добавляете кнопку в ваш фрейм, как показано в листинге 4.

Листинг 4. Добавление кнопки в ваш фрейм

				
DemoFrame::DemoFrame(const wxString& title)
      : wxFrame(NULL, wxID_ANY, title) {
   wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
   this->SetSizer(sizer);
   wxButton *button = new wxButton(this, wxID_CLOSE, "Click Me");
   sizer->Add(50, 50);
   sizer->Add(button, 0, wxALL, 50);
}

Перед добавлением кнопки во фрейм вы создаете калибратор (sizer). Калибратор является wxWidgets-эквивалентом менеджеров схем языка программирования Java: калибраторы позволяют вам использовать предопределенные правила для размещения объектов в вашем окне вместо необходимости указания размера и позиции каждого виджета по отдельности. В данном случае вы используете wxBoxSizer, который размещает виджеты на прямой линии (этот калибратор может быть вертикальным или горизонтальным). После создания калибратора вы присоединяете его к фрейму при помощи метода SetSizer(). Затем вы создаете кнопку. Параметры для конструктора кнопки таковы:

  • Родительский виджет (в данном случае фрейм)
  • ID, имеющий тип integer
  • Отображаемый на кнопке текст

Вам не нужно явно добавлять кнопку во фрейм; достаточно указания фрейма в качестве родительского контейнера. Однако, вы должны явно добавить кнопку в калибратор, для того чтобы алгоритм схемы размещения калибратора знал о ней. Это делается в последней строке метода, но не перед добавлением пустой области размером 50 x 50 пикселей в начале строки. При добавлении кнопки вы указываете также калибратору окружить кнопку границей толщиной в 50 пикселей. Это делается путем использования флага wxALL и последнего аргумента - числа 50.

Определение обработчика событий

Наконец, вы определяете простой обработчик событий, показанный в листинге 5.

Листинг 5. Простой обработчик событий

				
void DemoFrame::OnButtonPress(wxCommandEvent& event) {
   Close(true);
}

Более простого приложения вы сделать не сможете. Откомпилируйте его и увидите симпатичное окно с одной кнопкой, показанное на рисунке 2. Нажмите кнопку, окно закроется. В wxWidgets закрытие последнего родительского фрейма вызывает выход из приложения, поэтому нажатие на кнопку вызывает завершение работы приложения.

Рисунок 2. Демонстрационное окно
Рисунок 2. Демонстрационное окно

Данный пример, естественно, только поверхностно затрагивает возможности wxWidgets.

wxPython

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

Самой зрелой и полностью разработанной интерфейсной оболочкой wxWidgets является wxPython, при помощи которой вы можете создавать wxWidgets-программы, используя язык программирования Python. Загружаемые пакеты существуют для платформ Microsoft Windows, Mac и Linux, а также имеется довольно большое и активное сообщество пользователей. Как все это выглядит? Python-программа, показанная в листинге 6, создает точно такое же пустое окно, которое вы создали ранее на C++.

Листинг 6. Python-приложение, отображающее пустое окно

				
#!/usr/bin/env python

import wx

class DemoApp(wx.App):

   def OnInit(self):
      frame = DemoFrame(parent=None, id=-1, title='DeveloperWorks')
      frame.Show()
      return True

class DemoFrame(wx.Frame):

   def __init__(self, parent, id, title):
      wx.Frame.__init__(self, parent, id, title)
      sizer = wx.BoxSizer(wx.VERTICAL)
      self.SetSizer(sizer)
      button = wx.Button(self, wx.ID_CLOSE, "Click Me")
      sizer.Add((50, 50))
      sizer.Add(button, 0, wx.ALL, 50)
      self.Bind(wx.EVT_BUTTON, self.OnButtonClick)

   def OnButtonClick(self, event):
      self.Close(True)

if __name__ == "__main__":
   app = DemoApp()
   app.MainLoop()

Как вы можете видеть, в этой программе существует практически однозначное соответствие между API-вызовами на C++ и wxPython-вызовами. В обоих случаях вы создаете объект приложения и объект фрейма. Вы также начинаете с метода OnInit() и определяете похожие конструкторы и обработчики объектов.

Самым большим отличием в данном конкретном примере является связывание событий с обработчиками. Там, где C++-версия выполняет это связывание при помощи макросов таблицы событий, Python-версия использует метод Bind(), который принимает в качестве аргументов Python-объект, представляющий тип события и метод, вызываемый при наступлении события. Такая структура использует способность Python рассматривать методы как переменные и передавать их в качестве аргументов точно так же, как типы string или integer.

Преимущества wxPython перед C++ wxWidgets начинают проявляться в больших или более сложных программах. Если не отвлекаться на дебаты об относительных преимуществах C++ и Python как языков программирования, существуют некоторые отличные функциональнее возможности, присущие wxPython-версии, которые могут быть привлекательны для вас. Механизм обработки событий с использованием методов Bind() более легко вписывается в wxPython-версию, чем в wxWidgets. В Python-версии легче динамически обновить ваши обработчики во время исполнения. Некоторые сложные или составные виджеты, такие как древовидные списки или зависимая кнопка с изображением, являются стандартными в wxPython, но их нет в C++-версии. Также wxPython содержит пакет средств разработки Py, который делает тривиальной задачу добавления интерактивной отладки в вашу wxPython-программу.

wxEverythingElse

Python - это не единственный язык программирования, имеющий средства доступа к библиотеке wxWidgets. Хотя wxPython является самым зрелым пакетом, стоит оценить и другие пакеты, если вы предпочитаете работать с конкретным языком программирования. Давайте кратко рассмотрим некоторые другие пакеты из wxWorld. Обратите внимание, пожалуйста, на то, что оценка стабильности и функциональности этих проектов зависит от доступных материалов. Многие из этих проектов являются результатом труда одного или двух программистов. Если вас интересует проект для конкретного языка, проверяйте его самостоятельно.

wxPerl

Последняя основная версия wxPerl вышла в июне 2006. Предоставляются ежедневные копии, но доступная документация устарела на несколько лет. Активность списка рассылки составляет 2-3 сообщения в день. Бинарные загрузочные файлы доступны для Win32, Linux и Mac OS X. Кроме основного набора инструментальных средств доступно также несколько дополнительных наборов, включая OpenGL-оболочку и упаковщик для создания приложений Mac OS X.

Главной задачей в wxPerl является преобразование wxWidgets API в уникальную Perl-разновидность объектно-ориентированного программирования (OOP). Показанный в листинге 7 фрагмент создает фрейм, аналогичный рассмотренному выше примеру.

Листинг 7. Пример окна в wxPerl

				
package MyFrame;

use base 'Wx::Frame';

use Wx::Event qw(EVT_BUTTON);

sub new {
   my $class = shift;
   my $self = $class->SUPER::new(undef, -1, 'Trying wxPerl',
      [-1, -1], [250, 200]);
   my $sizer = Wx::BoxSizer->new(wxVERTICAL);
   my $button = Wx::Button->new($self, -1, 'Click me!', [-1, -1],
      [-1, -1]);
   EVT_BUTTON($self, $button, \&OnButtonClick);
   $sizer->Add($button);
   $self->SetSizer($sizer);
   return $self;
}

sub OnButtonClick {
   my($self, $event) = @_;
   $self->SetTitle('You Did It');
}

Этот код является, в сущности, построчным переводом исходного кода с C++ и Python, который вы уже видели. В данном случае библиотека wxWidgets поставляется в виде Perl-пакета, а также используется вызов функции EVT_BUTTON, который выглядит аналогично определению макроса в C++-версии.

wxRuby

Проект wxRuby находится в несколько не простом состоянии. Существует сырая программа, в которой связывание с wxWidgets API осуществляется вручную. Последняя версия этой программы была выпущена в ноябре 2004, и с этого времени продолжалась (с перерывами) разработка версии, которая использует более мощную программу Simplified Wrapper and Interface Generator (SWIG) для генерирования связей между Ruby и wxWidgets. Новая версия была недавно описана в списке рассылки как почти готовая: "Через пару дней…, или, возможно, в течение нескольких месяцев".

Одним интересным моментом относительно wxRuby является то, что в отличие от большинства других wxWidgets-оболочек у разработчиков есть возможность подгонять имена API-вызовов wxWidgets, для большего соответствуя соглашению о наименованиях в Ruby (например, lower_case_with_underscores вместо wxWidgets UpperCaseWithCamelCase ). То есть, в то время как весь приведенный выше код примеров использовал функцию SetSizer(), wxRuby вызывает set_sizer(). За исключением этого часть wxRuby-программы, работающая с wxWidgets API, будет практически аналогична приведенным выше

Мир wxWidgets

Другие порты wxWidgets находятся на разных стадиях завершенности или незавершенности. Ниже представлен краткий экскурс в область wxWidgets:

  • wxBasic - это интерпретатор языка Basic и набор средств связывания с wxWidgets. Находится в процессе выпуска новой версии (последняя бета-версия вышла в мае 2006 года).
  • wxEuphoria последний раз была выпушена в декабре 2005 года; представляет собой набор средств связывания wxWidgets с языком программирования Euphoria.
  • wxJS - это JavaScript-порт wxWidgets. Он протестирован только на системах Windows и включает исполняемые файлы для запуска JavaScript-сценариев. Он тоже последний раз выпускался в мае 2006 года. Разработчики анонсировали, что следующая версия будет поддерживать Linux.
  • wxLua - это набор средств связывания с языком программирования Lua. Он является кросс-платформенным и имеет относительно небольшой размер; последняя версия была выпущена в марте 2006 года.
  • Проект wx.NET связывает C# с wxWidgets. Последняя версия выходила в июле 2005 года.

Резюме

Мир wxWidgets может много чего предложить программистам всех мастей. Базовый набор программ является гибким и может удовлетворить большинство ваших GUI-потребностей. Различные пакеты для связывания позволяют wxWidgets находиться в пределах досягаемости для большинства программистов. Использование инструментального набора wxWidgets с предпочитаемым языком программирования поможет вам создать отлично выглядящие интерфейсы для ваших собственных приложений.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=3493