Глава     19

Учебный пример: среда разработки


Разделы

Содержание

Как мы уже отмечали в главе═18, среда разработки ≈ это совокупность классов, которые определяют структуру, необходимую для решения конкретной проблемы. Возможно, наиболее часто среды разработки используются при создании графических интерфейсов пользователя. Действительно, в большей степени популярность объектно-ориентированных техник можно отнести за счет успеха сред типа GUI (Graphics User Interface ≈ графический интерфейс пользователя).

В этой главе мы рассмотрим очень простую среду разработки═≈ Малую Среду Разработки LAF (LAF ≈ Little Application Framework). Она сознательно сделана гораздо проще, чем большинство коммерческих сред, поскольку рассчитана только на объяснение концепции среды разработки. LAF в целом содержит менее дюжины классов, в то время как в большинстве коммерческих сред их более сотни. Несмотря на это, среда LAF вполне закончена и демонстрирует большинство важных концепций, необходимых для понимания средств разработки графических приложений.

Среда LAF была создана с учетом требований переносимости на различные платформы. В то время как детали реализации могут сильно различаться, интерфейс LAF для различных платформ остается постоянным. Существуют версии среды LAF для Macintosh, PC и UNIX-систем.

19.1. Компоненты GUI

Разделы

Среда LAF поддерживает следующие компоненты GUI: единственное окно, меню, кнопки, текстовые поля, работу с мышью и клавиатурой.

Основной способ, посредством которого программист задает конкретный вид элемента управления (кнопки, меню, окна),═≈ это наследование. Так, существует общий класс кнопок button, из которого порождаются подклассы, то есть кнопки конкретного типа. То же самое справедливо для меню, пунктов меню и окон.

До того как объяснять структуру этих компонентов в среде LAF, нам следует вначале приобрести общее понимание программ, управляемых событиями, к которым относятся и интерфейсы.

19.2. Выполнение, управляемое событиями

Разделы

Первое концептуальное препятствие, которое должен преодолеть программист в попытке понять технику разработки графического интерфейса,═≈ это принцип построения программы на основе событий.

Традиционная программа следует своему собственному сценарию. Мы можем представить себе выполнение как указку, двигающуюся по программе от начала до конца. На разных этапах программа взаимодействует с пользователем, однако набор ответов, с которыми она способна справиться, весьма ограничен. Например, программа может задавать пользователю вопросы и реагировать только на простые ответы типа ╚да/нет╩, вводимые с клавиатуры.

Программа, управляемая событиями, контролируется пользователем. Она должна реагировать только на действия пользователя, и ее исполнение в основном сводится к единственному циклу. Следующий пример описывает с помощью псевдокода главный блок типичной программы, управляемой событиями. Инициализация начинает, а чистка памяти заканчивает приложение. В промежутке между ними программа просто повторяет цикл, ожидая, пока пользователь сделает следующий шаг═≈ например, передвинет мышь, нажмет на клавишу или вставит дискету в дисковод. В ответ на активность пользователя следует реакция программы, а затем она застывает в ожидании следующего действия.

program main;
begin
  initialize application;

  while not finished do
  begin
    if user has done something interesting
    then respond to it
  end;

  clean up application;
end

╚Интересные вещи╩, которые делает пользователь, называются событиями. Поскольку поток управления диктуется последовательностью событий, это называется выполнением, управляемым событиями. Не все события должны соответствовать действиям пользователя. Например, такие действия, как появление дискеты в дисководе, могут вызвать последовательность событий, генерируемых изнутри программы. Программа отреагирует на них таким же образом, как и на события, вызванные пользователем.

Объектно-ориентированное программирование естественным образом соответствует работе, управляемой событиями, потому что поток управления хорошо структурирован. Относительно просто создать библиотеку классов, которые имитируют реакцию на типичные события, но не выполняют никаких реальных действий. Путем порождения подклассов из классов такой библиотеки и переопределения некоторых методов мы добавляем ╚рабочую╩ часть приложения без необходимости переписывать ╚управляющие╩ разделы. Это в точности то, что делается средой разработки GUI-приложений.

Стив Барбек из фирмы Арple описал типичную библиотеку, используемую для построения управляемого событиями приложения, как ╚библиотеку наизнанку╩ [Wilson,═1990]. В традиционной программе библиотека поставляет части кода, а задачей программиста является ╚склеить╩ их в единое целое. В системе GUI приложение уже ╚склеено╩, а программист просто наделяет конкретными свойствами отдельные части библиотеки, используя порождение подклассов и переопределение методов, чтобы получить поведение, характерное для данного приложения.

19.3. Настройка через наследование

Разделы

Основное средство, посредством которого среда разработки настраивается на создание новой программы, ≈ это наследование. Среда предоставляет один или несколько абстрактных надклассов, которые будут переопределены программистом. Чтобы объяснить механизм настройки, мы разделим методы, определенные в этих классах, на три категории:

  1. Базовые методы. Они обеспечивают выполнение реальных действий, которые полезны для адаптации программы под задачу, но которые с большой вероятностью не будут переопределяться программистом. Примером служат методы, рисующие окружности, прямые линии и другие графические объекты в окне.
  2. Алгоритмические методы. Они описывают абстрактный алгоритм, а конкретизация оставляется на долю других методов. В качестве примера можно привести метод, который реализует описанный выше цикл ожидания события. Такие методы обеспечивают структуру типичного приложения без выполнения какой-либо фактической работы. Как и в случае базовых методов, алгоритмические методы обычно не переопределяются новым приложением.
  3. Абстрактные методы. Они выполняют фактическую работу приложения. В библиотеке среды разработки абстрактный метод, как правило, является просто пустой процедурой, которая ничего не делает. Путем переопределения абстрактных методов обеспечиваются реальные действия конкретного приложения.

При построении новой программы в среде разработки программист просто порождает несколько подклассов тех классов, которые поставляются средой приложения, переопределяя при этом абстрактные методы с целью задания необходимого поведения, специфичного для данной задачи. Среда разработки предоставляет заготовку основной программы (╚верхняя╩ часть приложения) и зачастую обеспечивает определенную базовую функциональность (╚нижняя╩ часть), оставляя программисту заполнение деталей посередине.

19.4. Классы в среде LAF

Разделы

В среде LAF имеется шесть основных классов: application, button, menu, menuItem, staticText и editText. Три класса, на основе которых обычно порождаются подклассы,═≈ это application, button и menuItem. Они подробно описаны ниже.

Мы создаем приложение путем порождения подклассов класса application, переопределяя в них некоторые методы. Основной цикл ожидания событий начинается с вызова метода run, унаследованного от класса application. Этот метод реализует цикл ожидания, описанный выше. Выполнение заканчивается, когда пользователь выбирает элемент меню quit, который закрывает окно приложения, то есть вызывает метод quit(), наследуемый из класса application.

Простейшее возможное приложение показано ниже. Обратите внимание, что его код состоит из:

class simpleApp : public application
{
public:

    simpleApp() : application("simple application")
     { }
};

void main()
{
  simpleApp theApp;
  theApp.run();
};

В данном случае приложение просто отображает окно на экране и ждет, пока пользователь его закроет.

19.5. Класс application

Разделы

Функции, предоставляемые классом application, показаны в табл.═19.1. Функция run()═обычно является последней функцией, вызываемой в процедуре main. Функция run() не имеет возвращаемого результата. Метод quit() служит для прекращения работы приложения.

Методы update() и clearAndUpdate() вызываются каждый раз, когда действия пользователя могут привести к обновлению экрана. Чаще всего это необходимо вследствие какого-либо события, например перемещения мыши или нажатия

Таблица═19.1. Методы класса application

клавиши на клавиатуре. Метод clearAndUpdate() сначала очищает экран перед обновлением. Чтобы выполнить обновление, вызывается метод paint(). Обычно пользователь не обращается к методу paint() непосредственно.

Метод paint() перерисовывает изображение на экране дисплея. Обычно в классах приложения он переопределяется, чтобы обеспечить требуемое поведение. Он не должен непосредственно вызываться пользователем═≈ данный метод запускается как реакция на вызов методов update() или clearAndUpdate().

Если нажата кнопка мыши, вызывается метод mouseButtonDown(). Два его целочисленных аргумента представляют собой координаты курсора относительно окна приложения на момент нажатия кнопки. Поведение по умолчанию, связанное с этим методом,═≈ не делать ничего. Чтобы выполнить какое-либо действие, данный метод должен быть переопределен в подклассе класса application.

Событие, связанное с нажатием мыши, обычно не вызывает каких-либо действий, непосредственно влияющих на изображение на экране. Соответствующий метод просто записывает всю необходимую информацию и затем вызывает либо update(), либо clearAndUpdate() для перерисовки экрана.

Когда пользователь нажимает клавишу на клавиатуре, вызывается метод keyPressed(). В качестве аргумента он получает символ, эквивалентный нажатой клавише.

Методы top(), bottom(), left() и right() возвращают соответствующие координаты окна приложения относительно экрана, выраженные в пикселях. Обратите внимание на то, что эти величины даются в глобальной системе координат, где точка (0,0)═≈ это верхний левый угол экрана. Почти все прочие функции, которые оперируют с координатами, используют значения в системе координат окна приложения, где точка (0,0)═≈ это верхний левый угол окна.

В классе application доступны несколько элементарных процедур рисования и вывода на печать. Обычно вызовы графических функций выполняются во время обновления экрана. Если используется метод clearAndUpdate(), то любые действия, связанные с отображением графики, выполненные до вызова этой процедуры, будут потеряны. Таким образом, вызовы графических подпрограмм обычно производятся (прямо или косвенно) из метода paint(). Координаты для рисования даются в той же системе отсчета, что и для нажатия мыши. Иначе говоря, величина (0,0) представляет собой верхний левый угол окна, значения по оси OX увеличиваются вправо, а значения по оси OY═≈ вниз. Последнее часто противоречит ожиданиям начинающих программистов.

19.5.1. Класс button

Класс application

Разновидности кнопок создаются путем порождения подклассов из класса button и переопределения виртуального метода pressed. Как только кнопка присоединена к окну, этот метод вызывается автоматически при нажатии кнопки:

class quitButtonClass : public button
{
public:

    quitButtonClass (window * win)
      : button (win, "Quit",═5,═5,═20,═50)
     { }

protected:

    pressed (window * win)
     {
       ...
     }
};

Окно приложения передается в качестве аргумента конструктору кнопки. В═процессе инициализации кнопке приписываются некие координаты относительно окна, дабы гарантировать, что кнопка будет изображаться правильно. Чаще всего кнопка описывается как часть состояния нового класса приложения и инициализируется в конструкторе класса приложения, как это показано ниже:

class newApplication : public application
{
public :
    newApplication : application ("new program"),
             quitButton(this)
     { }
    ...

private:
    quitButtonClass quitButton;
};

Обычно при нажатии на кнопку реакция заключается в вызове функции-члена класса приложения. Мы можем определить единственный класс общего назначения, который поддерживает это действие, и таким образом избежим необходимости определять новые классы для каждого типа кнопки. Поскольку C++ является языком со строгим контролем типов данных, новый класс должен использовать шаблон и параметризироваться с помощью нового класса приложения1. Шаблон выглядит следующим образом:

template <class T>
class tbutton : public button 
{
public:

    tbutton (window * win, char * t,
             int x, int y, int h, int w,

    void (T::*f)() )
     		: button(win, t, x, y, h, w), fun(f)
     { }

protected:
    void (T::* fun) ();
    virtual void pressed (window * win)
     {
      (((T *) win)->*fun) ();
     }
};

Последний из аргументов, f, служит указателем на функцию-член и должен соответствовать какому-нибудь методу класса Т. Мы должны признать, что синтаксис, используемый для описания указателя на функцию-член, является довольно бестолковым. Эта функция запоминается в переменной fun и вызывается при нажатии кнопки. С применением этого класса приложение с двумя кнопками может быть создано следующим образом:

class helloApp : public application
{
public:

    helloApp()  : 
      application("hello world"),
      quitButton (this, "quit",═5,═5,═50,═20, quit),
      clearButton (this, "clear",═5,═30,═50,═20,    
        clearAndUpdate)
      { };

    virtual void mousButtonDown(int, int);

private:

    tbutton <helloApp> quitButton;
    tbutton <helloApp> clearButton;
};

19.5.2. Классы menu и menuItem

Класс application

Меню во многих отношениях похожи на кнопки. В среде LAF существуют два класса для обслуживания меню: menu и menuItem. Первый класс представляет собой категорию линеек меню, а второй═≈ конкретный элемент этой категории. Когда пользователь указывает на меню, на экране высвечиваются соответствующие элементы меню. Когда мышью выбирается элемент меню, выполняется метод selected для элемента меню. Нужное действие элемента меню обеспечивается за счет создания подкласса класса menuItem и переопределения метода selected. Это происходит так же, как и в случае с кнопками.

Меню обычно объявляются как поля данных в классе приложения и инициализируются конструктором, что иллюстрируется следующим примером:

сlass clearMenuItem : public menuItem
{
public:

    clearMenuItem (menu & m, char * t) : menuItem(m, t)
     { }

protected:
    virtual void selected (window *);
};

class helloApp : public application
{
public:

    helloApp ()
     : application("hello world")
       clearMenu (this, "Clear"),
       clearItem (clearItem, "clear/C")
     { };

    virtual void mouseButtonDown(int, int);

private:

    menu clearMenu;
    clearMenuItem clearItem;
};

void clearMenuItem::selected (window * w)
{
  w->clear () ;
}

Класс tmenuItem аналогичен классу tbutton. Если вы помните, класс tbutton устраняет необходимость создания нового класса кнопок, так как все, что делает кнопка═≈ это вызывает функцию-член. Аналогичным образом класс tmenuItem наследует от класса menuItem и добавляет новый аргумент, который должен быть функцией-членом. Функция, передаваемая в качестве добавочного аргумента, должна вызываться при выборе соответствующего элемента меню.

template <class T>
class tmenuItem : public menuItem
{
public:

    tmenuItem (menu & m, char * t, void (T::* f) () )
      : menuitem(m, t), fun(f)
     { }

protected:

    void (T::* fun) ();
    virtual void selected (window * win)
     {
      (((T*) win)->*fun)();
     }
};

Прочие классы среды LAF позволяют программисту размещать в окне текстовые поля (в том числе редактируемые). Эти свойства среды LAF здесь не описаны: подробную документацию по среде LAF, а также исходные тексты можно найти по адресу ftp://ftp.cs.orst.edu/pub/budd/laf.

19.6. Резюме

Разделы

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

Упражнения

Разделы

  1. Постройте в среде LAF простое приложение с тремя типами кнопок.
  2. Какие три группы методов можно выделить в типичном приложении среды? Приведите примеры методов каждой категории для класса application в среде LAF.
  3. Проследите передачу управления различным классам, начиная с нажатия пользователем командной кнопки и заканчивая моментом, когда программа возвращается к ожиданию следующего события. Предположите, что программист выдает команду update в конце подпрограммы, связанной с щелчком мышью, чтобы вызвать обновление экрана.

Учебный пример: среда разработки