Главная страница «Первого сентября»Главная страница журнала «Информатика»Содержание №7/2008


Семинар

Язык Java и его возможности

Продолжение. Начало см. в № 6/2008

Часть II. Интерактивность

В предыдущей публикации мы познакомились с общими принципами и основными возможностями языка Java. Если смотреть на данный язык как на технологию повышения выразительности web-страниц (а такой аспект действительно имеет место, хотя это и не единственное назначение Java), то главными достоинствами программ-аплетов следует назвать их интерактивность и динамичность.

В части II будет рассмотрена первая из названных черт Java-аплетов. На тщательно выбранных простых примерах автор постарается показать, какими средствами располагает язык для обеспечения взаимодействия с пользователем. Картина несколько осложняется тем, что в последнее время модель этого взаимодействия была существенно усовершенствована, и новая версия даже получила специальное название Java 2. Возможно, программисты и получили определенные выгоды от этих нововведений, но с точки зрения изложения материала проблем это добавило. Автор после некоторых размышлений выбрал следующую стратегию разбора примеров: сначала предлагается вариант аплета на “старой” (ставшей классической и описанной во многих книгах, а также более лаконичной и простой) модели взаимодействия Java с пользователем, а затем демонстрируется, как это выглядит в новой версии. Учитывая, что материал носит познавательный характер, такой подход кажется оправданным. Тем же, кто заинтересуется языком Java и захочет его применять, вероятно, стоит сразу “сместить акценты” в сторону Java 2, поскольку практика многократно подтверждала, что пытаться задерживать развитие — дело безнадежное.

Строго говоря, интерактивность подразумевает как наличие развитых средств ввода данных, так и их вывода. В данной публикации упор в основном сделан на первую половину, поскольку средства графического вывода в Java хотя и кратко, но уже обсуждались в части I.

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

1. Что такое интерактивность

Интерактивным режимом исполнения программы принято называть такой, при котором пользователь может оперативно вмешиваться в процесс обработки данных и изменять его ход. Термин “интерактивность” происходит от английского слова interaction, что в буквальном переводе означает “взаимодействие”. Это довольно широкое и абстрактное понятие, применяемое к разным областям человеческой деятельности1. Данная статья, естественно, не претендует на философское рассмотрение проблемы и во многом опирается на интуитивно здоровое представление об интерактивности как интенсивном взаимодействии при наличии альтернативных вариантов реакции. В частности, отличие просмотра видеофильма на мониторе и компьютерная игра, где на пятом уровне потребуется мешочек с перцем, предусмотрительно захваченный с третьего, серьезной проблемы для оценки степени интерактивности не вызывает. Кстати говоря, в частном случае анализа web-страниц, в массе своей представляющих статически организованную (заранее подготовленную) информацию, появление возможности влияния на контент страницы со стороны пользователя трудно не заметить.

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

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

“Свет в конце тоннеля” показался только к концу второго поколения, когда начали возникать языки программирования. В них уже появились операторы ввода, благодаря которым в принципе можно было считать данные в момент исполнения программы. Однако на больших ЭВМ это мало помогало, поскольку задачу все равно пропускал оператор, а он, не зная содержания задачи, естественно, не мог внести в нее никакой интерактивности. В таких случаях все вводимые значения заранее подготавливались на машинных носителях и в нужный момент автоматически читались согласно алгоритму, который был заложен в программу. В то же время, успехи в производстве позволили создать достаточно компактные “малые” ЭВМ, которые уже не требовали специального машинного зала и помещались в комнате типового размера. Распространенным образцом подобной техники были, например, ЭВМ семейства “Наири”, разработанные в Ереванском НИИ математических машин. Особенность работы ЭВМ такого класса состояла в том, что с ними уже могли непосредственно общаться подготовленные программисты, а операторы лишь время от времени помогали им в случае возникновения нестандартных ситуаций (вроде замены перфоленты, восстановления работоспособности путем повторного включения и т.п.).

На рис. 1 изображено типичное средство диалога того времени — электрифицированная пишущая машинка “Consul” (рисунок отсканирован из пожелтевшей от времени “антикварной” документации).

Рис. 1. Первое средство интерактивности — электрифицированная пишущая машинка

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

Еще большее удобство в ведении диалога (пока все еще текстового) предоставил появившийся алфавитно-цифровой дисплей, который позволил отказаться от бумаги и сделал процесс управления ЭВМ более оперативным. Одной из первых моделей ЭВМ, где применялся дисплей (со световым пером!), был “МИР-2”2 (см. фото из архива автора на рис. 2). Машина была разработана в Киевском институте кибернетики АН УССР и содержала целый ряд оригинальных решений.

Как это ни странно, третье поколение добавило к интерактивным возможностям не так много. Дело в том, что ЭВМ этого поколения были большими и ориентировались на многозадачную коллективную обработку. Оператор мог оперативно влиять на ход вычислительного процесса, но он занимался в основном организацией прохождения потока заданий через машину, а не поддержкой каждого отдельного задания. Некоторая интерактивность проявлялась в многопользовательском режиме разделения времени, когда к большой ЭВМ присоединялось множество удаленных терминалов. Пользователь, сидя за таким терминалом, получал некоторые возможности руководить работой своей задачи.

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

Первоначально диалог носил характер “обмена репликами”, когда человек вводил команду, а компьютер расшифровывал ее и исполнял, а если оказывался не в состоянии, то объяснял почему. Как известно, такой интерфейс взаимодействия между человеком и машиной называется командной строкой. Но сейчас ушел в прошлое и этот режим. Господствует графический интерфейс, который минимизирует набор с клавиатуры и оперирует специальными виртуальными органами управления — кнопками, меню, списками, полями ввода и т.п. Во многом новый интерфейс опирается на вновь возросшие аппаратные возможности — мощный графический дисплей и манипулятор “мышь”. Не вызывает сомнения, что дальнейшие успехи в развитии программной и аппаратной составляющих компьютеров приведут к появлению новых методов взаимодействия пользователей со своими электронными помощниками.

Таким образом, мы видим, что интерактивность отнюдь не является “врожденным” свойством вычислительной техники, но, напротив, возникает на определенном этапе ее развития. Методы интерактивного взаимодействия постоянно совершенствуются, поэтому и языки программирования должны параллельно предоставлять программистам все новые и новые возможности в этой области.

2. Об интерактивности в Java 1 и 2

Обработка ввода

Как нам уже известно из общих принципов реализации языка Java, взаимодействие аплета с аппаратной частью происходит через посредство Java-машины. Взаимодействие построено на основе механизма событий, широко распространенного в современных системах программирования. Фиксируя изменения в состоянии клавиатуры или мыши, Java-машина генерирует соответствующее событие (часто используют более точный термин сообщение о событии) и информация об этом передается всем компонентам. В классической версии языка, которую мы иногда для краткости будем называть Java 1, весь этот процесс происходит “по умолчанию”, т.е. программист не должен делать по этому поводу никаких описаний. Если мы хотим, чтобы наш класс, например, производный от Applet, реагировал на некоторое событие, необходимо написать соответствующий обработчик. Отсутствие обработчика не является ошибкой — просто в таком случае аплет на данное событие реагировать не будет. Скажем, нет ничего удивительного в том, что аплету не требуется отслеживать ввод с клавиатуры, а все управление производится с помощью мыши.

Полный список событий клавиатуры и мыши для Java 1 приведен в следующей таблице3.

Что касается Java 2, то в ем концепция взаимодействия существенно модифицирована. Главное новшество заключается в том, что оповещение о событиях перестает быть автоматическим и требует непосредственного участия программиста. Чтобы класс обрабатывал некоторое событие, необходимо выполнить следующие обязательные шаги:

· унаследовать соответствующий интерфейс “слушателя” (Listener) события с помощью оператора implements;

· зарегистрироваться в качестве слушателя события, используя один из методов addXXListener(this), где XX — разновидность слушателя, например, Key или Mouse;

· определить все функции, входящие в интерфейс (обязательность определения всех методов любого интерфейса является беспрекословным требованием языка).

Заметим, что последний шаг можно упростить, если использовать дополнительный к интерфейсу объект-адаптер. В этом случае можно не указывать “ненужные” методы, но мы не будем рассматривать этот вариант: нам кажется, что на первый раз хватит усложнений, связанных с объектом-“слушателем”.

В Java 2 определен класс KeyListener для клавиатуры и два “слушателя” (по-видимому, из технологических соображений) — MouseListener и MouseMotionListener для мыши. Охватываемые ими события перечислены в таблице на с. 5.

По-видимому, отсутствие избыточного оповещения всех неиспользуемых при вводе объектов улучшает эффективность внутреннего функционирования Java-машины, чего явно нельзя сказать по поводу улучшения читаемости программ. Причем три дополнительных шага при работе со “слушателями”, которые были перечислены выше, это еще не все дополнительные строки в Java 2 по сравнению с лаконичным текстом в Java 1. Читатели будут иметь возможность сопоставить тексты на Java 1 и 2 при знакомстве с приводимыми ниже примерами. Именно из-за более сложного описания одних и тех же процессов в Java 2 в статье рассмотрение начинается с Java 1.

А как же сейчас обстоит дело с программами, написанными на Java 1? Пока они компилируются, хотя при этом выдается предупреждение о применении “нерекомендуемых” (“осужденных”, как часто переводят оригинальный термин deprecated) методов. Вот как это комментируют профессионалы [1] (насколько эта оценка справедлива, судите сами, сопоставив имеющиеся ниже тексты на Java 1 и 2).

“Java 1.4.0 содержит 22 осуждаемых класса, 8 осуждаемых интерфейсов, 50 осуждаемых свойств и свыше 300 осуждаемых методов и конструкторов. Некоторые, такие, как List.preferredSize() и Date.parseDate(), осуждены потому, что уже есть методы, которые делают то же самое лучше. Другие, такие, как thread.stop() и thread.resume(), осуждены, поскольку они с самого начала были плохой идеей и по-настоящему опасны. В любом случае, что бы ни было причиной осуждения метода, это предполагает, что мы не будем им пользоваться.

Согласно официальному заявлению Sun, “рекомендуется изменение программ таким образом, чтобы устранить использование осуждаемых методов и классов, хотя пока не планируется полное удаление таких методов и классов из системы”. Избавьтесь от них прямо сейчас. Благодаря этому Java станет проще, чище и безопаснее”.

Интерфейс События Комментарии

KeyListener

keyPressed()

возникает при нажатии клавиши клавиатуры

keyReleased()

возникает при отпускании клавиши клавиатуры

keyTyped()

возникает при вводе символа

MouseListener

mousePressed()

возникает при нажатии

mouseReleased()

возникает при отпускании кнопки мыши

mouseClicked()

возникает при щелчках кнопок мыши

mouseEntered()

возникает при помещении указателя на компонент

mouseExited()

возникает при выходе указателя с компонента
 

 

MouseMotionListener

mouseMoved()

возникает при перемещении мыши

mouseDragged()

возникает при перемещении мыши с нажатой кнопкой

Графический интерфейс

К графическому интерфейсу принято относить окна и их органы управления: кнопки, списки, меню и т.п. Чтобы программистам легче было создавать окна и их компоненты, в языках программирования имеются специальные библиотеки классов: MFC, Motif, OpenLook, Qt, Tk, Xview, OpenWindows и множество других. Каждый класс такой библиотеки описывает какой-либо графический компонент.

В технологии Java дело осложняется тем, что ее продукты должны работать в любой или хотя бы во многих графических средах. Нужна библиотека классов, независимая от конкретной графической системы.
В первой версии JDKБ задачу решили наиболее простым образом: были разработаны интерфейсы, содержащие методы работы с графическими объектами, на которые ссылаются все классы библиотеки AWT. Понятно, что для работы с экраном в каждой конкретной графической среде интерфейсы надо реализовывать отдельно, с помощью графических библиотек, которые используются в данной среде. Такие интерфейсы были названы peer-интерфейсами. При выводе
Java-объекта, основанного на таком интерфейсе, на экране создается парный ему (peer-to-peer) объект графической подсистемы данной операционной системы, который собственно и отображается. Поэтому графические объекты AWT имеют вид, характерный для этой графической среды. Именно из-за рассмотренной выше реализации интерфейсов и других методов, написанных главным образом на языке C++, приходится для каждой платформы выпускать свой вариант JDK.

В версии JDK 1.1 библиотека AWT была переработана. В нее добавилась возможность создания компонентов, полностью написанных на Java и не зависящих от peer-интерфейсов. Такие компоненты стали называть “легкими” (lightweight) в отличие от компонентов, реализованных через peer-интерфейсы, названных “тяжелыми” (heavy). “Легкие” компоненты везде выглядят одинаково, сохраняют заданный при создании вид (look and feel). Более того, приложение можно разработать таким образом, чтобы после его запуска можно было выбрать какой-то определенный вид вплоть до Windows 95, и сменить этот вид в любой момент работы. Эта интересная особенность “легких” компонентов получила название PL&F (Pluggable Look and Feel), или plaf.

Была создана обширная библиотека “легких” компонентов Java, названная Swing. В ней были переписаны все компоненты библиотеки AWT, так что Swing может использоваться самостоятельно, несмотря на то, что все классы из нее расширяют классы библиотеки AWT. На рис. 3 изображен пример приложения, интерфейс которого базируется на Swing, — это Topic Map for e-Learning Editor [2] (редактор карт предметной области для e-learning).

Рис. 3. Пример приложения с интерфейсом из компонентов Swing

Сначала библиотека классов Swing поставлялась как дополнение к JDK 1.1. В состав Java 2 SDK она включена наряду с AWT как основная графическая библиотека классов, реализующая идею “стопроцентно чистая Java”.

В Java 2 библиотека AWT значительно расширена добавлением новых средств рисования, вывода текстов и изображений, получивших название “Java 2D”, и средств, реализующих перемещение текста методом DnD (Drag and Drop). Кроме того, в Java 2 включены новые методы ввода/вывода Input Method Framework и средства связи с дополнительными устройствами ввода/вывода, такими, как световое перо или клавиатура Бройля, названные Accessibility. Все эти средства Java 2: AWT, Swing, Java 2D, DnD, Input Method Framework и Accessibility — составили библиотеку графических средств Java, названную JFC (Java Foundation Classes).

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

3. Примеры интерактивных аплетов

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

Обработка ввода

Пример 1. Аплет, управляемый с клавиатуры

В качестве примера простейшего интерактивного аплета рассмотрим, как можно на языке Java описать управление некоторым графическим объектом с помощью курсорных клавиш со стрелками. Для простоты объектом, который пользователь сможет перемещать, будет служить обыкновенный небольшой черный квадрат.

Рис. 4. Аплет, управляемый с клавиатуры

Как мы уже неоднократно подчеркивали, по соображениям безопасности аплет тщательно изолирован от компьютерного “железа” и получает всю информацию о нем через посредство информационных каналов, организованных в той или иной форме. Проще говоря, аплет в принципе не в состоянии контролировать аппаратные средства (в данном случае — клавиатуру), а получает сообщения об их состоянии в форме, определяемой устройством Java-машины.

Типичный способ подобного взаимодействия заключается в предоставлении аплету методов обработки стандартных событий, связанных с тем или иным устройством. Из сказанного очевидно, что логика программирования здесь определяется не столько логикой функционирования оборудования, сколько логикой построения самого языка Java. И самое печальное для начинающих знакомиться с Java: поскольку язык постоянно совершенствуется, то меняется и само описание методов взаимодействия с устройствами. Как мы уже упоминали ранее, в классической версии Java и в Java 2 работа со вводом и выводом пишется по-разному. К счастью, пока компилятор поддерживает обе идеологии, и примеры из старых книжек по Java пока еще можно реализовать.

В первоначальном варианте языка достаточно было написать обработчик требуемого события. В нашем примере будет задействовано только одно из событий под названием keyDown, которое возникает, когда пользователь нажал произвольную клавишу. Наша задача — описать на Java, как аплет должен отреагировать на это событие.

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

Итак, обратимся к листингу 1, показывающему, как выглядит решение нашей задачи об управлении визуальным объектом с помощью клавиатуры в версии Java 1.

Листинг 1

import java.applet.*;
import java.awt.*;
public class movingObject extends Applet {
   int x = 20; int y = 20; 
   //начальные координаты объекта
   int r = 10; //и его размер
   public boolean keyDown(Event e, int c) {
      switch (c) {
        case 1004: y--; break; //вверх
        case 1005: y++; break; //вниз
        case 1006: x--; break; //влево
        case 1007: x++; break; //вправо
      }
      repaint(); //перерисовать!
      return true; 
      //клавиша обработана, система не будет
      // ее анализировать
    }
   public void paint(Graphics g){
    g.drawRect(0,0,99,99); //нарисовать рамку
    g.fillRect(x,y,r,r); 
   //нарисовать объект из точки (x, y)
   }
} 

Пропустив стандартные начальные строчки (об их расшифровке мы подробно говорили в первой части публикации), отметим только, что переменные x и y задают первоначальные координаты в площади аплета, а r определяет размер стороны квадратика.

Сосредоточим наше внимание на методе keyDown, предназначенном для обработки нажатия клавиш. Сразу обратим внимание на список аргументов метода, который располагается в скобках. Объект типа Event есть экземпляр события, в котором сосредоточена вся системная информация о нем; нам эта информация не потребуется. А самое важное — код нажатой клавиши — вынесено отдельно в переменную типа int: ее-то анализом мы и займемся. Удобно для этой цели воспользоваться оператором-переключателем switch, который описывает поведение при различных альтернативных значениях переменной c.

Примечание. Несколько слов о том, откуда взялись коды 1004–1007. Для их определения автор воспользовался простейшим аплетом, написанным ранее и легкодоступным в Интернете по адресу [3]. Обязательно обратите внимание на то, что величина числовых значений существенно превышает 256: вместо “старого доброго” ASCII Java везде использует Unicode. Можно было в качестве альтернативы использовать константы LEFT, RIGHT, UP, DOWN из класса Event, но поскольку в Java 2 используются другие обозначения, было принято решение (возможно, спорное) обойтись без этих “вымирающих” констант.

Пусть, для примера, была нажата клавиша со стрелкой вниз. В этом случае по ее коду (1005) будет выбрана вторая альтернатива, и координата объекта y будет увеличена на единицу: следуя традициям языка Си, запись y++ кодирует оператор присвоения y = y + 1.

Примечание. Об этом как-то не принято вспоминать, но причина появления столь известной записи, по-видимому, восходит к ассемблеру. У многих процессоров, в том числе и у того, на котором разрабатывался Си, помимо обычной команды сложения произвольных чисел ADD, существует специализированная инструкция увеличения числа на единицу INC, которая, как и все специализированное, во многих отношениях эффективнее. Язык Си, будучи языком системного программирования, не мог “пройти мимо” этой особенности, поскольку команды инкрементирования и декрементирования переменных встречаются в алгоритмах очень часто (вспомните хотя бы работу с массивами).

Итак, нажатие клавиши управления курсором приводит к пересчету соответствующей координаты, после чего остается перерисовать объект на новом месте. Это осуществляется с помощью вызова метода перерисовки изображения repaint( ).

Наконец, последняя строка обработчика обеспечивает возврат значения true, которое является сигналом Java-системе о том, что нажатие обработано и никаких действий со стороны последней не требуется. Для нашего учебного примера эта тонкость несущественна, но в реальных задачах от нее может зависеть разумность поведения аплета.

Метод paint читается легко: он обеспечивает рисование рамки вокруг площади аплета и перерисовку нашего черного квадрата.

Часть HTML-текста, обеспечивающая изображенный на рис. 4 вид, приведена ниже.

<ul>
<li><b>Щелкните мышкой</b> внутри площади аплета;
<li>двигайте объект с помощью клавиш 
        управления курсором.
</ul>
<APPLET
     CODE = "movingObject.class"
     WIDTH = 100
     HEIGHT = 100>
</APPLET> 

Первоначальный щелчок кнопкой мыши, о котором говорится в первом пункте инструкции, является обязательным.

В ходе компиляции листинга 1 нас ожидает неприятный сюрприз в виде следующего “весьма понятного” предупреждения:

Note: movingObject.java uses or overrides a deprecated API.

Note: Recompile with -Xlint:deprecation for details.

(Замечание: movingObject.java использует или переопределяет нерекомендуемый4 API. Перекомпилируйте с –Xlint:deprecation для получения деталей.)

Если воспользоваться “мудрым” советом и перекомпилировать программу с указанным ключом, получим более подробное пояснение:

movingObject.java:8: warning: [deprecation] keyDown(java.awt.Event,int) in java.awt.Component has been deprecated

public boolean keyDown(Event e, int c) {

1 warning

(“строка 8: предупреждение: [возражение (протест, порицание, осуждение, неодобрение)] keyDown(java.awt.Event,int) в java.awt.Component был осужден”; далее указывается место ошибки и выдается общее количество предупреждений).

В переводе на доступный язык это означает, что компилятор “сильно не рекомендует” пользоваться методом keyDown. Тем не менее он “не смеет” отвергнуть допустимую в ранних версиях конструкцию и потому ограничивается предупреждением (warning).

Посмотрим, как будет выглядеть решение той же самой задачи на языке версии Java 2, к чему нас так настойчиво подталкивал “заботливый” компилятор.

Листинг 2

//Java 2
import java.applet.*; 
import java.awt.*;
import java.awt.event.*;
public class movingObject2 extends Applet
implements KeyListener 
{ //аплет использует интерфейс KeyListener
   int x = 20; int y = 20; 
  //начальные координаты объекта
   int r = 10; //и его размер
   public void init(){
   this.addKeyListener(this); 
  //добавим к аплету "слушателя клавиатуры"
}
public void paint(Graphics g)
{   g.drawRect(0,0,99,99); 
  //нарисовать рамку
  g.fillRect(x,y,r,r); 
  //нарисовать объект из точки (x, y)
}
public void keyPressed(KeyEvent e)
{  int key = e.getKeyCode();
   switch(key){
      case KeyEvent.VK_LEFT: {x--; break;}
      case KeyEvent.VK_RIGHT:{x++; break;}
      case KeyEvent.VK_UP: {y--; break;}
      case KeyEvent.VK_DOWN: {y++; break;}
                     }
   repaint(); //перерисовать!
}
//А эти события нам не нужны, но "пустые"
// обработчики необходимы
public void keyReleased(KeyEvent e){}
public void keyTyped(KeyEvent e){}
}

В заголовке обращает на себя внимание дополнение implements KeyListener, которое указывает на применение в классе (аплете) названного интерфейса. Как уже отмечалось ранее, назначение класса-“слушателя” состоит в том, чтобы следить за сообщениями от соответствующего объекта, в нашем случае клавиатуры. Объект Listener создается в момент старта аплета при выполнении стандартной процедуры init: к объекту со ссылкой this, т.е. аплету, он добавляется посредством метода addKeyListener( ).

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

Наиболее интересно внимательно сопоставить “новый” метод keyPressed( ) со “старым” keyDown( ), текст которого мы разбирали ранее по листингу 1. Видны следующие принципиальные различия:

· метод keyDown( ) возвращает значение булевского типа, а keyPressed( ) — нет, о чем говорит служебное слово void в его описании; по указанной причине в листинге 2 нет оператора return, который мы видели в листинге 1;

· код нажатой клавиши не является параметром нового метода: данные о нем извлекаются из информации о событии при помощи специального метода getKeyCode( ).

Все остальные отличия менее существенны, в частности, вместо конкретных числовых значений в тексте программы использованы константы библиотеки java.awt.event. Подобный способ записи является более универсальным и не требует знания ненужных аппаратных деталей.

По мнению автора, “старая” модель взаимодействия с клавиатурой выглядит проще; во всяком случае, текст листинга 1 объективным образом существенно короче. Однако тем, кто собирается и дальше продолжать знакомство с языком Java, видимо, более дальновидно осваивать методы и приемы Java 2.

Пример 2. Аплет, взаимодействующий с мышью

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

Рис. 5. Аплет, взаимодействующий с мышью

Как и в предыдущем случае, рассмотрение начнем со “старой” версии Java, а затем сравним результирующую программу с реализацией на Java 2.

В обоих случаях примем следующий несложный алгоритм рисования. При нажатии кнопки мыши координаты точки, в которой это произошло, запомним и будем считать начальными. Затем, пока кнопка удерживается, будем отслеживать перемещение мыши, каждый раз проводя при этом линию в текущую точку. “Старые” линии будем предварительно убирать путем их повторной прорисовки белым цветом. Для простоты алгоритма не будем заботиться о фиксации нарисованных линий, т.е. как только мы начнем рисовать мышью новую линию, предшествующая немедленно исчезнет.

Программа рисования на Java 1 имеет следующий текст (см. листинг 3).

Листинг 3

import java.applet.*;
import java.awt.*;
public class mouseDraw extends Applet {
  int x0,y0; //координаты начальной точки
  int x1,y1; 
 //координаты предпоследней точки
  int x2,y2; 
  //координаты последней точки
  boolean drawing=false; 
  //режим рисования (пока выключен)
  public boolean mouseDown(Event e, int x, int y)
  {
   x0 = x; y0 = y; 
   //запоминаем координаты начальной точки
   drawing = true; 
   //включаем режим рисования
   return true; //обработано
  }
  public boolean mouseUp(Event e, int x, int y)
  {
   drawing=false; 
   //выключаем режим рисования
   return true; //обработано
  }
  public boolean mouseDrag(Event e, int x, int y) 
  {
     if (drawing) {
     x2 = x; y2 = y; 
     //запоминаем координаты новой точки
        repaint(); //перерисовать!
        x1 = x2; y1 = y2; 
        //теперь последняя точка стала 
        // предпоследней!
        }
     return true; //обработано
     }
   public void paint(Graphics g){
     g.drawRect(0,0,199,199); //рамка
     g.setColor(Color.white); //рисуем белым
     g.drawLine(x0,y0,x1,y1); // линия
     //стереть старую линию
     g.setColor(Color.black); //рисуем черным
     g.drawLine(x0,y0,x2,y2); 
     //нарисовать новую
     }
}

Программа начинается с описания используемых переменных, которые доступны всем ее методам. Именно через эти переменные осуществляется взаимодействие; например, координаты начальной точки x0 и y0 фиксируются в методе paint( ). Логическая переменная drawing в методе mouseDown( ) включает и в mouseUp( ) выключает режим рисования линий; собственно рисование инициируется методом mouseDrag( ).

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

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

Текст web-странички, в которую встраивается аплет, приводить не будем, поскольку он очень похож на приведенный в примере 1. Отметим лишь, что размер отведенного под аплет пространства увеличен до 200 x 200 (единственное место программы, где это существенно, — рисование рамки в методе paint( ) ).

При переходе к версии Java 2 снова появляются “слушатели” событий, причем для мыши, как мы знаем, их два: первый — MouseListener отвечает за отслеживание кнопок, а второй — MouseMotionListener отслеживает движение.

Предлагаем читателям разобрать листинг 4 самостоятельно (по аналогии с тем, как это уже было сделано для листинга 2).

Листинг 4

//Java 2
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class mouseDraw2 extends Applet 
implements MouseListener, MouseMotionListener {
  int x0,y0; //координаты начальной точки
  int x1,y1; //координаты предпоследней точки
  int x2,y2; //координаты последней точки
  boolean drawing=false; 
  //режим рисования (пока выключен)
  public void mousePressed(MouseEvent e) 
  {
    x0 = e.getX(); y0 = e.getY(); 
    //запоминаем координаты начальной
    точки
    drawing = true; 
    //включаем режим рисования
  }
       
  public void mouseReleased(MouseEvent e) {
        drawing = false; 
        //выключаем режим рисования
                                            }
  public void mouseDragged(MouseEvent e) 
  {
    if (drawing) {
        x2 = e.getX(); y2 = e.getY(); 
        //запоминаем координаты новой точки
        repaint(); //перерисовать!
        x1 = x2; y1 = y2; 
        //теперь последняя точка стала 
        //предпоследней!
                            }
  }
    public void paint(Graphics g){
        g.drawRect(0,0,199,199); //рамка
        g.setColor(Color.white); //рисуем белым
        g.drawLine(x0,y0,x1,y1); // линия
        //стереть старую линию
        g.setColor(Color.black); //рисуем черным
        g.drawLine(x0,y0,x2,y2); 
        //нарисовать новую
                                 }
    public void init() { 
    //Создадим MouseListener и
    //MouseMotionListener, связанные с мышью 
    //и ее перемещением
        this.addMouseListener(this) ;
        this.addMouseMotionListener(this);
                                          }
    //Другие, не используемые методы 
    //интерфейса MouseListener:
        public void mouseClicked(MouseEvent e) 
        { } 
        public void mouseEntered(MouseEvent e) { } 
        public void mouseExited(MouseEvent e) 
        { } 
    //Неиспользуемый метод интерфейса 
    //MouseMotionListener:
    public void mouseMoved(MouseEvent e) { }
}
Графический интерфейс

Пример 3. Учимся работать с классами

Математики нередко используют следующий способ оптимизации в доказательстве теоремы: когда оно становится чрезмерно длинным и труднообозримым, некоторую его относительно самостоятельную часть выносят и доказывают отдельно (такую вспомогательную часть называют леммой). Мы сейчас поступим аналогичным образом, с той лишь разницей, что в отличие от математической наша “Java-лемма” будет иметь самостоятельную ценность. Автор намерен вынести из примера 4 то, что не имеет непосредственного отношения к интерактивности, но зато демонстрирует, как в Java работают с классами. Основная часть описанного в примере 3 — тексты программы — будет включена в пример 4 безо всяких изменений.

Итак, как в языке Java используются классы? Рассмотрим задачу изображения на экране простейших геометрических фигур, ограничившись ради компактности квадратом и кругом. Наиболее инициативные читатели смогут легко расширить этот список, добавив по аналогии несколько собственных фигур.

Основу нашей будущей иерархической структуры геометрических фигур составит класс Figure, который описывает абстрактную геометрическую фигуру, лишенную конкретного визуального образа (см. рис. 6). Наиболее важными ее характеристиками являются размер (figureSize: для квадрата это сторона, а для окружности примем диаметр) и положение на экране (его будем, как водится, характеризовать координатами левого верхнего угла фигуры).

Рис. 6. Конструируем иерархию классов

Перечисленные поля класса Figure являются присущими любой фигуре, поэтому удобно их не описывать каждый раз, а автоматически наследовать. Как видно из рассматриваемого рисунка, соответствующие поля из родительского класса Figure “тиражируются” в порождаемые классы для квадрата (Square) и окружности (Circle). В последнем классе логично дополнительно создать поле R, описывающее радиус окружности; естественно, в остальных классах оно отсутствует за ненадобностью.

Мы уже знаем, что каждый класс в Java имеет один или несколько конструкторов, т.е. методов, порождающих конкретные экземпляры (реализации) данного класса. Каждый из трех наших классов будет иметь собственный конструктор (его имя совпадает с именем класса), но конструкторы-потомки могут (и в нашем примере будут!) вызывать конструктор родительского класса Figure для выполнения стандартных действий по инициализации любой фигуры независимо от ее конкретного типа.

Наконец, рассмотрим самый эффектный метод, который рисует фигуру на экране, — drawFigure( ). Этот метод есть у всех геометрических фигур, поэтому его целесообразно описать в родительском классе. Но, с другой стороны, каждая фигура рисуется по-своему, так что возникает проблема, что именно должен содержать данный родительский метод. Возникшее противоречие в Java решается путем объявления подобного метода абстрактным, т.е. пока лишенным содержания, но в классах-потомках это будет исправлено путем перекрытия метода. Конкретизация абстрактных методов в классах-потомках является обязательным: Java-машина “хочет” иметь хоть какие-то инструкции на случай вызова такого метода и поэтому добивается этого от программиста на этапе трансляции.

Нельзя не упомянуть тот простой, но важный факт, что каждый объект-потомок переопределяет абстрактный метод drawFigure( ) по-своему, так что при обращении к нему выполняемые действия однозначно зависят от конкретного класса объекта. В теории ООП подобное фундаментальное свойство носит название полиморфизм.

Заметим, что, обсуждая наш демонстрационный пример, мы увидели в нем проявление всех фундаментальных свойств и понятий ООП: классы, экземпляры классов, наследование (родитель—потомок), полиморфизм (перекрытие методов), конструкторы, а также абстрактные классы и методы. При желании на этом же примере можно обсудить и инкапсуляцию как основу построения объектов.

Как теперь описать всю эту богатую   картину на Java? Обратимся к листингу 5.

Листинг 5

import java.applet.*;
import java.awt.*;
abstract class Figure { 
//объект-родитель: геометрическая фигура "вообще"
// поля объекта
   int figureSize; //размер
   int x; int y; //координаты (левый верхний угол)
// методы объекта
   Figure(int s, int x, int y) { //конструктор
   this.figureSize = s; this.x = x; this.y = y;
        }
//абстрактный метод рисования фигуры — пока он "пустой"
   abstract void drawFigure(Graphics g);
        }
class Circle extends Figure { 
//конкретная реализация фигуры: окружность
// поля объекта
   int r; //радиус — в программе не используется, 
                       //введен в демонстрационных целях
//методы объекта
   Circle(int s, int x, int y) { //конструктор
      super(s,x,y); //вызов конструктора
      суперкласса (там все есть!)
      r = s/2; //вычислим радиус окружности —
      специфика класса
      }
   void drawFigure(Graphics g) { //метод рисования окружности
        g.drawOval(x,y,figureSize,figureSize);
        }
  }
  class Square extends Figure { 
//конкретная реализация фигуры: квадрат
  //методы объекта
    Square(int s, int x, int y) { //конструктор
        super(s,x,y);
        }
    void drawFigure(Graphics g) { //метод рисования квадрата
        g.drawRect(x,y,figureSize,figureSize);
        }
  }
  public class Figures extends Applet { //собственно аплет
  public void paint(Graphics g) { //прорисовка
    g.drawRect(0,0,279,279); //нарисовать рамку
    Figure c; //рабочая переменная
    for (int i = 1; i < 10; i++)
      for (int j = 1; j < 10; j++)
        {if ( (I + j)%2 == 0) {c = new Circle(20,25*i,25*j);}
             else {c = new Square(20,25*i,25*j);}
        c.drawFigure(g);
        }
  }
}

В классе Figure описываются поля figureSize, x, y, которые в дальнейшем будут унаследованы классами-потомками. Далее определяется конструктор с тремя параметрами, который будет создавать новые экземпляры класса и заполнять их поля значениями, указанными в качестве параметров при вызове метода. Обратим внимание читателей на использование служебного слова this которое заменяет собой имя любого создаваемого объекта. Наконец, завершает описания объявление абстрактного метода рисования фигуры drawFigure( ). Стоит заметить, что у метода пока нет (и на этом этапе не может быть!) тела — после заголовка отсутствуют фигурные скобки.

Далее в листинге следует класс Circle, порожденный от Figure (extends Figure). В нем, как и следовало ожидать, поля figureSize, x, y даже не упоминаются, поскольку они будут автоматически переданы по наследству безо всяких усилий со стороны программиста. Зато специфическое для окружности поле с радиусом описывать необходимо.

Очень интересен конструктор метода класса Circle. Сначала он вызывает стандартный компьютер суперкласса, который уже “умеет” раскладывать три переданных ему числовых параметра в передаваемые по наследству вышеперечисленные поля. Затем дописана специфическая для данного класса часть; в нашем случае это вычисление радиуса.

Завершает описание класса Circle метод рисования окружности, базирующийся на стандартной графической процедуре drawOval( ).

Класс Square устроен аналогично, поэтому предлагаем читателям разобрать строки с его описанием самостоятельно.

Итак, мы рассмотрели всю приведенную в листинге 5 иерархию классов. При желании читатели могут ее дополнить и другими фигурами: прямоугольником, треугольником и т.д.

Далее по тексту листинга следует объявление класса Figures5. Это начинается собственно аплет. Обязательно сопоставьте объявления класса Figures с уже анализировавшимися выше объявлениями для Circle и Square. Их вид одинаков с точностью до обозначений! Иными словами, разобранный пример наследования полезен для понимания механизма работы самого аплета. В самом деле, единственное отличие между наследованием от классов Applet и Figure заключается в том, что текст последнего мы написали сами и видим в листинге. Зато все остальное работает совершенно единообразно: в классе-предке имеются абстрактные методы (для Figure это drawFigure( ), а для Applet — paint( ) или init( ) ), которые конкретизируются в ходе дальнейшего наследования классов. Таким образом, оказывается, что в ходе написания аплетов мы уже пользовались перекрытием абстрактных методов родительского класса, не зная, правда, как это работает. Теперь знаем.

Собственно аплет примера 3 носит демонстрационный характер и рисует узор 9 ? 9 из чередующихся квадратов и окружностей (см. рис. 7).

Рис. 7. Аплет, демонстрирующий классы

Алгоритм рисования несложен. Если сумма переменных цикла I + j делится нацело на 2 (знак “%” означает остаток от деления), то создается экземпляр класса окружность, иначе — класса квадрат. Размер обоих объектов фиксирован и равен 20, а координаты пересчитываются через значения i и j, что и позволяет создать требуемый узор. Обращает на себя внимание, что при вызове метода drawFigure( ) Java-машина самостоятельно выбирает вид фигуры в зависимости от типа переменной c — мы наблюдаем полиморфизм в действии!

После успешной компиляции аплета обратите внимание на то, что в каталоге для каждого класса появляется свой собственный файл с расширением .class, который содержит байт-код.

Как уже отмечалось выше, пример 3 был задуман не ради создания узора, который изображен на рис. 7. Сам узор является лишь индикатором правильности написания программы, а главной целью было подготовить иерархию классов геометрических фигур, используя которую мы сможем быстро реализовать интерактивный пример 4 по разработке аплета, создающего экземпляры фигур-объектов по нашему желанию. К нему мы и переходим.

Пример 4. Аплет, создающий объекты под руководством пользователя

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

Рис. 8. Аплет, создающий объекты под руководством пользователя

Аплет содержит следующие органы управления: связанные в радиогруппу кнопки “квадрат/окружность”, поля ввода размера и координат и кнопка “Ввод”, инициирующая построение фигуры с установленными параметрами. Видно, что интерфейс аплета сильно похож на типичное диалоговое окно графической среды Windows.

Особенностью данного аплета является сохранение создаваемых объектов-фигур в специальном массиве. Согласно принципам языка Java, массивы можно создавать только из элементарных типов, каковым объект не является6. Для сохранения объектов предназначен специальный класс — Vector, который определен в библиотеке java.util. С его помощью мы и будем сохранять сконструированные объекты. Очень полезной особенностью класса Vector является возможность автоматического увеличения его размера по мере заполнения. Иными словами, в отличие от массива количество компонентов структуры Vector не ограничивается первоначально заявленным, а может произвольно расширяться в процессе работы.

Рассмотрим листинг 6, описывающий решение поставленной задачи (cм. с. 14).

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

Примечание. Не забудьте набрать специфическую для данного аплета третью строку, обеспечивающую импорт библиотеки с классом Vector!

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

Сначала создается группа радиокнопок с именем cbg (традиционное имя для checkbox group), в которую включаются кнопки выбора формы фигуры. Помимо выводимой подписи и принадлежности к указанной группе, конструктор также содержит логический параметр, определяющий начальное состояние кнопки; в нашем случае при запуске аплета выбранной окажется кнопка “Квадрат”.

Далее следуют пары компонентов Label (метка, т.е. подпись) и TextField (поле ввода), обеспечивающие ввод параметров каждого из объектов-фигур. В качестве параметров каждого из конструкторов указаны начальные значения, которые будут предлагаться при старте. Наконец, последним органом управления является кнопка “Ввод”, запускающая считывание параметров и создание нового объекта.

Все перечисленные выше визуальные органы управления создаются при вызове метода init( ), который запускается при старте аплета. Возможно, наиболее внимательные читатели обратили внимание на то, что в тексте нигде не фигурируют координаты и размеры этих диалоговых компонентов. Причина в том, что по умолчанию Java-машина устанавливает их расположение и размер самостоятельно. Обычно в литературе такой стиль расположения компонентов сравнивают с текстовым редактором, который производит размещение слов по строкам без каких-либо указаний со стороны пользователя. Очевидно, что при такой “политике” размещение компонентов зависит от ширины поля, выделенного под аплет; приведенное на рис. 8 изображение получено автором при ширине рабочей области 280 пикселей. Разумеется, в Java существуют и более “жесткие” методы расположения визуальных компонентов, но в рамках данной публикации мы не будем их касаться.

Далее по тексту программы следует метод action( ), который вызывается Java-машиной при любой манипуляции пользователя. В нашем случае реакция метода будет наблюдаться только при нажатии кнопки “Ввод” (см. условие e.target instanceof Button7), во всех остальных случаях — переключение радиокнопок или набор чисел в поля ввода, все необходимые действия Java-машина будет выполнять автоматически.

Примечание. Метод action является главным и единственным инструментом Java 1. Как “по-новому” реализуется обработка визуальных органов управления в Java 2, показано в примере 5.

Итак, при нажатии кнопки “Ввод” запускается программа-обработчик, создающая объект-фигуру в соответствии с параметрами, установленными пользователем. Для этого считанные из полей ввода строки преобразуются в числовую форму при помощи метода Integer.parseInt( ) и запоминаются в соответствующие переменные, а затем с их учетом и в соответствии с состоянием переключателя “квадрат/окружность” формируется объект c либо класса Square, либо Circle.
С помощью метода addElement( ) созданный объект добавляется для хранения к вектору. Для того чтобы он появился на экране, вызывается метод repaint( ).

Листинг 6

import java.applet.*;
import java.awt.*;
import java.util.*; //здесь класс Vector
abstract class Figure {
//объект-родитель: геометрическая фигура "вообще"
// ...
// Здесь помещается соответствующий текст из примера 3
// ...
        g.drawRect(x,y,figureSize,figureSize);
        }
}
public class Figures extends Applet {
//собственно аплет
//описание визуальных компонентов ввода
        CheckboxGroup cbg = new CheckboxGroup();
        Checkbox kvad = new
        Checkbox("квадрат",cbg,true);
        Checkbox okr = new
        Checkbox("окружность",cbg,false);
        Label ls = new Label(" размер");
        TextField ts = new TextField("10");
        Label lx = new Label("X");
        TextField tx = new TextField("100");
        Label ly = new Label("Y");
        TextField ty = new TextField("100");
        Button enter = new Button("Ввод");
        Vector v = new Vector (10,5); //массив из
        10 объектов, добавление по 5
public void init() { //создание компонентов при старте аплета
        this.add(kvad); this.add(okr); //форма
        this.add(ls); this.add(ts); //размер
        this.add(lx); this.add(tx); //X
        this.add(ly); this.add(ty); //Y
        this.add(enter); //"Ввод"
                 }
public boolean action(Event e, Object o) {
//реакция на кнопку ввода
     if (e.target instanceof Button) {
        int s = Integer.parseInt(ts.getText());
        //считать размер из поля ts
        int x = Integer.parseInt(tx.getText()); //X
        int y = Integer.parseInt(ty.getText()); //Y
        Figure c; //рабочая переменная
        if (kvad.getState()) //квадрат (или
        окружность)?
             {c = new Square(s,x,y);}
          else {c = new Circle(s,x,y);}
        v.addElement(c); //добавить объект в вектор v
        repaint(); //перерисовка
        return true; //обработка состоялась
        }
      return false; //обработки не было
}
public void paint(Graphics g){
//прорисовка
      g.drawRect(0,0,279,299); //нарисовать рамку
      g.drawLine(0,70,280,70); //граница органов управления
      for (int i = 0; i<v.size(); i++) {
       //отобразить все объекты
        Figure f = (Figure) v.elementAt(i);
          f.drawFigure(g);
}

Особый интерес представляет рисование всех созданных на данный момент фигур, которые хранятся в массиве класса Vector. Алгоритм необычайно компактен и в прямом смысле слова помещается в двух строках:

for (int i = 0; i<v.size(); i++)

{ //отобразить все объекты

Figure f = (Figure) v.elementAt(i);

f.drawFigure(g);}

Элементы из вектора извлекаются циклически, начиная с нулевого номера, причем их текущее количество определяется методом size( ). Далее создается рабочая переменная f класса Figure, значение которой формируется следующим образом. Сначала из вектора извлекается очередной объект по его номеру i — это обеспечивает метод elementAt(i). Для единообразия считанный объект преобразуется к классу Figure с помощью операции преобразования типа (Figure)8 и сохраняется в указанную переменную. Далее для нее вызывается метод drawFigure(g), где g есть графический контекст аплета, в результате чего в нужном месте экрана воспроизводится квадрат или окружность нужного размера. Приятное удивление вызывает тот факт, что хотя переменная f имеет обобщенный тип Figure, она отображается совершенно правильным образом — в очередной раз спасибо корректно реализованному полиморфизму.

Пример 5. Работа с визуальными компонентами в Java 2

Заключительная задача в нашем кратком знакомстве с интерактивностью языка Java — создание аплета, моделирующего один из вопросов теста (см. рис. 9). В отличие от предыдущего примера программа данного аплета написана в соответствии с правилами Java 2.

Как видно из рисунка, ответ на вопрос заключается в нажатии на одну из связанных радиокнопок, которая соответствует правильному варианту, и затем подтверждении своего выбора нажатием виртуальной кнопки “Ввод”. В результате в зависимости от выбранного варианта происходит переход к одной из специально подготовленных web-страниц.

Примечание. Как и в примере 4, мы вновь сталкиваемся с проблемой регулирования расположения визуальных компонентов в окне аплета. И, как в предыдущем случае, пока мы решаем ее подбором ширины изображения (рис. 9 соответствует значению ширины 200 пикселей).

Описание поддержки перечисленных органов управления реализовано в программе, приведенной в листинге 7.

Для контроля за виртуальными органами управления в Java 2 предусмотрены специальные “слушатели”, причем для разных компонентов они могут отличаться. В частности, для радиогруппы (совокупности радиокнопок) используется интерфейс под названием ItemListener, а для кнопки — ActionListener; именно на них указывают ссылки в заголовке нашего аплета.

Далее следуют описания визуальных компонентов ввода и их создание в окне аплета, которое обеспечивается стартовым методом аплета init( ). Там же создаются соответствующие “слушатели”. В демонстрационных целях для “слушателей” радиокнопок используется два принципиально разных способа “подключения”: для кнопок pb и cg — единый “централизованный слушатель” с методом itemStateChanged( ), а для inf — “встроенный” непосредственно внутрь описания кнопки объект, который создается при помощи конструктора ItemListener( ). В обоих случаях обработка состоит в выяснении текущего состояния контролируемой (“прослушиваемой”) кнопки радиогруппы с помощью метода getState( ), который возвращает булевское значение в соответствии с ее состоянием. По результатам анализа целочисленная переменная vybor получает значение, равное номеру нажатой кнопки; по нему и будет вынесено окончательное решение о правильности ответа.

Рис. 9. Аплет с визуальными приложениями

Еще раз подчеркнем, что оба использованных способа “прослушивания” равнозначны и вы можете применять любой из них. Попутно заметим, что созданный для кнопки inf встроенный класс, не имеющий имени, порождает вспомогательный файл press$1.class.

Остается разобрать устройство метода ActionPerformed( ), который вызывается при нажатии кнопки “Ввод”. Его первая часть анализирует текущее значение переменной vybor: если оно равно 3, что соответствует номеру кнопки с правильным ответом, то в результирующую переменную pageName заносится имя страницы verno.html, в противном случае — neverno.html. Названия говорят сами за себя, а что касается содержания указанных страниц, то оно может быть любым, в зависимости от идей тестирования и фантазии авторов.

А дальше начинается самое интересное — организуется переход на html-файл с заданным именем. Для этого с помощью метода getAppletContext( ) у исполняющей системы запрашиваются параметры аплета. Зная их, можно вызвать другой стандартный метод showDocument( ), который способен осуществить переход на новую страницу. Для формирования полного URL к имени файла добавляется путь к нему, узнаваемый посредством еще одного метода getCodeBase( ). Подчеркнем, что по соображениям безопасности аплет не может переходить на произвольный URL — рекомендуется поместить необходимые странички в том же самом каталоге, где находится сам аплет.

Следующая далее конструкция try/catch предназначена для реализации “опасных” действий, в результате которых может возникнуть ситуация грубой ошибки (в нашем случае речь идет об обработке ситуации с отсутствием необходимого файла). Описание механизма обработки критических ситуаций — самостоятельная глава языка Java, поэтому здесь мы просто скажем, что если файл существует, то он будет загружен в браузер.

Листинг 7

//Java 2
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
        
public class press extends Applet
  implements ActionListener, //Button
        ItemListener{ //Checkbox
//описание визуальных компонентов ввода
        CheckboxGroup cbg = new CheckboxGroup();
        Checkbox pb = new Checkbox("Playboy",cbg,true);
        int vybor=1; //результат выбора — сейчас 1
        Checkbox cg = new Checkbox("Cool girl",cbg,false);
        Checkbox inf = 
        new Checkbox("Информатика 1
        Сентября",cbg,false);
        Button enter = new Button("Ввод");
        public void init() { //создание компонентов при
        старте аплета
           this.add(pb); this.add(cg); this.add(inf); //издания
           pb.addItemListener(this); //подключение
           "централизованного" слушателя
           cg.addItemListener(this);
           inf.addItemListener(
                  new ItemListener() { //"встроенный" слушатель
                       public void itemStateChanged(ItemEvent e)
                                          { if (inf.getState()) vybor = 3;
                   }
                 });
        this.add(enter); //"Ввод"
        enter.addActionListener(this); //слушатель для кнопки
        }
public void itemStateChanged(ItemEvent e)
        {if (pb.getState()) vybor = 1; 
        if (cg.getState()) vybor = 2;
        }
public void actionPerformed(ActionEvent e) {
        String pageName="";
        if ((vybor==1) | (vybor==2)) pageName="neverno.html";
        if (vybor==3) pageName="verno.html";
        if (pageName != "") {
          AppletContext ac = getAppletContext();
          try {ac.showDocument(new URL(getCodeBase() + pageName));}
        catch (MalformedURLException u)
        {showStatus("URL not found!");}
              }
        }
 public void paint(Graphics g){
        g.drawRect(0,0,199,99); //нарисовать рамку
 }
}

Примечание. К сожалению, сетевые методы в Java не всегда работают четко, поэтому необходимо очень жестко соблюдать правила их применения. Вот пример из моей скромной практики: если перенести строку считывания контекста в начало аплета, то он… перестает запускаться!

Завершая обсуждение аплета, подчеркнем, что наш пример, разумеется, весьма далек от реального тестирования; в частности, наша простенькая программа не может воспрепятствовать подбору правильного ответа. Впрочем, это не такая простая проблема, как может показаться на первый взгляд…

Итак, сегодня мы освоили основные идеи Java-технологии, связанные с интерактивностью, и научились создавать взаимодействующие с пользователем аплеты. В следующий раз поговорим о создании динамических изображений, что также является одной из сильных сторон языка Java.

1 Харольд Э.Р. 10 причин, по которым нам нужен Java 3. http://www.javaportal.ru/articles/10_reasons_Java3.html.

2 Topic Maps 4 e-learning. http://compsci.wssu.edu/iis/nsdl/

3 Еремин Е.А. Хотите узнать код любого символа? http://educomp.org.ru/theory/info/keys.html.

Е.. А.. Еремин,
г. Пермь

TopList