Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
160
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

URL myObject; try {

myObject = new URL ( "http: //www.vmedia.com/index.html");

}

catch (MalformedURLException e) {

// код здесь выполняется, если строка URL неправильна

}

Мы будем подробно описывать исключения в главе 10, "Структура программы", так что не волнуйтесь, если пока это вам не очень непонятно. Только не забудьте, что, создавая новый URL, вы должны стараться перехватывать исключения. Если вы абсолютно уверены, что у вашего URL синтаксис правильный, вы можете не помещать никакого кода между вторыми фигурными скобками.

Есть еще другой важный конструктор URL, который получает URL и строку как параметры. URL указывает абсолютный базовый URL, а строка содержит путь к объекту относительно этого базового указателя. Например, если вы назначили http://www.vmedia.com/ourbook/ как URL и

строку "images/pictures.gif", новый URL укажет на http://www.vmedia.com/ourbook/images/picture.gif. Если же вы укажете строку "/otherbook/index.html", новый URL укажет на http://www.vmedia.com/otherbook/index.html.

Этот конструктор полезен вместе с методом getCodeBase класса Applet, который возвращает URL файла класса Applet. Вы можете использовать getCodeBase и относительный конструктор URL, чтобы создавать URL на объекты без определения базового имени. Это особенно полезно, потому что апплетам в целях безопасности не позволено открывать сетевые соединения с удаленными главными компьютерами, за исключением того Web-сервера, с которого файл этого класса был загружен (мы обсудим эти ограничения более полно в главе 13, "Работа с сетью на уровне сокетов и потоков"). Итак, вы должны установить ресурсы, которые будет использовать ваш апплет, на том же Web-сервере, что и сам апплет. При построении URL к этим ресурсам в апплете просмотра самое простое и самое безопасное - использовать метод getCodeBase с относительным конструктором URL вместо того, чтобы использовать абсолютный URL. Это упростит и установку вашего апплета на новом Web-сервере.

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

Доступ к параметрам

Особенность хороших языков высокого уровня - способность получать параметры из командной строки. Программисты используют это, чтобы изменить поведение программ, основываясь на вводимых пользователем данных, что уменьшает потребность в сложном пользовательском интерфейсе. Но Java-апплеты запускаются не из командной строки, поскольку они, как и их параметры "командной строки", являются вложенным внутренним HTML-кодом. Рассмотрим следующую Web-страницу.

Пример 5-4a. Web-страница с параметрами.

<HTML>

<HEAD>

<TITLE>Good Web Applet</TITLE> </HEAD>

<BODY>

<APPLET CODE="GoodWebApplet.class" WIDTH=250 HEIGHT=250> <PARAM NAME="IMAGE"

VALUE="http://www.vmedia.com/vvc/onlcomp/java/chapter5/images/sample.gif"> <PARAM NAME="SOUND" VALUE="http://www.vmedia.com/vvc/onlcomp/java/chapter5/sounds/sample.au"> </APPLET>

</BODY>

</HTML>

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

В этом фрагменте кода мы имеем вложенную переменную с именем IMAGE и значением ".. /images/sample.gif". Апплет может обращаться к переменной IMAGE, используя метод getParameter, который получает имя переменной в качестве параметра и возвращает строку, содержащую значение переменной. То же верно и для параметра SOUND. Все параметры представляются как строки.

СОВЕТ Из главы 6, "Интерфейс прикладного программирования", вы узнаете, как преобразовать переменные строкового типа к другим типам. Это вам пригодится, если вы захотите получить в качестве параметра число.

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

Пример 5-4b. Усовершенствованный апплет для Web. public void init() {

String ImageParam;

String SoundParam; resize(250,250);

// значения берутся из HTML-файла

ImageParam = getParameter("IMAGE"); SoundParam = getParameter("SOUND"); try {

// используем параметры, чтобы взять URL ImageURL = new URL(ImageParam);

SoundURL = new URL(SoundParam);

}

catch (MalformedURLException e) {} myImage = getImage(ImageURL); mySound = getAudioClip(SoundURL);

}

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

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

Взаимодействие с пользователем

Чтобы взаимодействовать с пользователем в реальном масштабе времени и отслеживать изменения в оболочке времени выполнения Java, апплеты используют понятие события (event). Событие - это информация, сгенерированная в ответ на некоторые действия пользователя (перемещение мыши или нажатие клавиши на клавиатуре). События также могут быть сгенерированы в ответ на изменение среды - к примеру, когда окно апплета заслоняется другим окном. Оболочка времени выполнения следит за происходящими событиями и передает информацию о событии особому методу, называемому обработчиком события (event handler). Многие обычно используемые обработчики событий предопределены для класса Applet. По умолчанию эти обработчики не делают ничего - чтобы использовать их, надо заместить соответствующий метод своим собственным кодом, например:

public boolean mouseMove(Event evt, int x, int y) {

// код, расположенный здесь, выполняется, если переместилась мышь return true; // событие обработано

}

www.books-shop.com

Метод mouseMove вызывается всякий раз, когда перемещается мышь. Обработав событие, он возвращает true.

События, генерируемые мышью

Обработчику событий mouseMove передаются три параметра: непосредственно событие, которое является классом, содержащим всю информацию, нужную для уникальной идентификации события, и две координаты - в данном случае новое расположение мыши внутри апплета. В табл. 5-2 приведены предопределенные обработчики событий.

Таблица 5-2. Часто используемые предопределенные обработчики событий

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

mouseDown (Event, Нажата кнопка мыши. Целочисленные параметры указывают расположение int, int) мыши.

mouseUp (Event, Кнопка мыши отпущена. int, int)

mouseMove (Event, Перемещение мыши. int, int)

mouseDrag (Event, Перемещение мыши с нажатой кнопкой. int, int)

mouseEnter (Event, Перемещение мыши на окно апплета. int, int)

mouseExit (Event,

Мышь покинула окно апплета.

int, int)

 

keyDown (Event,

Нажата клавиша перемещения курсора или функциональная клавиша.

int)

Целочисленный параметр указывает эту клавишу (см. табл. 5-3).

keyUp (Event, int)

Клавиша перемещения курсора или функциональная клавиша отпущена.

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

Пример 5-5. Апплет отображения курсора. import java.applet.*;

import java.awt.*; import java.net.*;

public class CursorApplet extends Applet { // расположение мыши

private int mouse_x, mouse_y; // хотите следовать за мышью? private boolean Follow = true; private Image CursorImage; public void init() {

mouse_x = 125; mouse_y = 125; resize(250,250);

String CursorFile = getParameter("CURSORFILE"); try {

URL CursorURL = new URL(CursorFile); CursorImage = getImage(CursorURL);

}

catch (MalformedURLException e) { CursorImage = createImage(0,0);

}

}

public void paint(Graphics g) {

//простая рамка g.drawRect(0,0,249,249);

//прорисовка курсора в месте расположения мыши

www.books-shop.com

g.drawImage(CursorImage,mouse_x,mouse_y,this);

}

public boolean mouseMove(Event evt, int x, int y) { if (Follow) {

//обновление информации о местоположении mouse_x = x;

mouse_y = y;

//перерисовка окна

repaint();

}

return true;

}

public boolean mouseDown(Event evt, int x, int y) {

//здесь слежение отключается if (Follow) {Follow = false;}

//а здесь - наоборот

else {Follow = true;} return true;

}

}

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

public boolean mouseDown ( Event evt, int x, int y){ if (evt.clickCount==l) ( // одиночное нажатие

}elseif (evt.clickCount==2) { // двойной щелчок

}else {

// слишком быстрые пальчики

}

return true;

}

Этот метод вызывается при нажатии кнопки мыши. Если кнопка нажата один раз, апплет обрабатывает случай с одиночным щелчком; если кнопка нажата дважды, апплет обрабатывает двойное нажатие.

События, генерируемые клавиатурой

Методы keyUp и keyDown работают таким же образом, как и обработчики событий мыши, за исключением того, что им передается идентификатор клавиши, а не координаты события. Идентификатор клавиши - это целое число, которое соответствует нажатой клавише. Обычные клавиши пишущей машинки имеют значения, соответствующие их ASCII-кодам. Кроме того, Java поддерживает множество специальных клавиш, приведенных в табл. 5-3. Эти специальные клавиши фактически определены как статические целые переменные (константы) для класса Event. В главе 6, "Интерфейс прикладного программирования", будет показано, как преобразовать целые числа в строки, если вы захотите отображать ввод с клавиатуры. Следующий фрагмент кода проверяет нажатие клавиш управления курсором:

Public boolean keyDown(Event evt, lnt key) { switch(key) {

case Event.UP: case Event.DOWN: case Event.LEFT: case Event.RIGHT: default:

}

return true;

www.books-shop.com

}

Метод keyDown вызывается при нажатии клавиши. Оператор switch переключается между четырьмя стрелками и всеми другими клавишами и вызывает соответствующий участок кода для рассматриваемой клавиши.

Shift, Ctrl & Meta

События могут быть обработаны по-разному в зависимости от того, нажата или нет какая-то из специальных клавиш наложения маски на происходящее событие. Shift, Ctrl и Meta - клавиши наложения маски в языке Java. Клавиша Meta эквивалентна клавише Alt в Microsoft Windows; под оконной системой X11 она может быть соотнесена с различными клавишами. Класс Event обеспечивает методы shiftDown, controlDown и metaDown, которые возвращают булевскую переменную, указывающую состояние каждой из клавиш наложения маски.

Обработчики событий: что же происходит на самом деле?

Что же действительно происходит при возникновении события? AppletContext обращает внимание на то, что событие произошло внутри апплета (см. врезку "Интерфейс AppletContext" выше в этой главе). AppletContext создает новый экземпляр класса Event, присваивает значения соответствующим параметрам, чтобы апплет знал, какое событие случилось, и передает новое событие методу handleEvent. Класс Event содержит всю информацию, нужную для идентификации события; переменные, описывающие события, перечислены в табл. 5-4.

 

Таблица 5-4. Переменные события

Переменная

Описание

public Object Компонент, в котором произошло событие для апплета; обычно непосредственно target сам апплет.

public long Время, в которое произошло событие, - целое число типа Long, содержащее число when миллисекунд, прошедшее с полуночи 1 января 1970 года по Гринвичу. В Java есть

метод java.lang.System.currentTimeMillis, который возвращает это значение. public int id Тип события (см. табл. 5-6).

public int x Координата X события. public int y Координата Y события.

public int key Идентификатор клавиши (для специальных клавиш, см. табл. 5-3).

public int Состояние клавиш наложения маски. modifiers

public Object Необязательный параметр. Он не используется для событий мыши и клавиатуры, arg но часто применяется при использовании графики, обеспечиваемой Java API (см.

главу 9, "Графика и изображения").

Метод handleEvent проверяет тип события и вызывает соответствующий обработчик, передавая ему относящиеся к делу параметры (диаграмма этого процесса показана на рис. 5-5).

Рис. 5.5.

Например, при перемещении мыши возникает событие MOUSE_MOVE, которое и передается методу handleEvent. Последний вызывает соответствующий этому событию метод mouseMove, передавая ему положение мыши. Фактически передавать положение события в виде отдельных параметров не нужно, потому что эта информация для удобства программирования закодирована непосредственно в событии.

Вы можете заменить метод handleEvent, если хотите обработать большое количество различных событий без замены каждого конкретного метода. Это может быть также полезно, если вы хотите обрабатывать последовательности событий по-другому. Сделав это, не забудьте,

www.books-shop.com

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

public boolean handleEvent(Event evt) { switch (evt.id) {

case Event.MOUSE MOUISE_DOWN: case Event.MOUSE MOUSE_UP: case Event.MOUSE MOUSE_MOVE: case Event.MOUSE MOUSE_DRAG: case Event.MOUSE MOUSE_ENTER:

case Event.MOUSE MOUSE_EXIT:

}

}

Вы можете генерировать события в своей программе. Для этого нужно просто создать новый экземпляр класса Event и объявить его в своем апплете (конструкторы событий перечислены в табл. 5-5). Генерация событий может быть полезной для написания системы макросов, которая, например, регистрирует движение мыши. Вы могли бы оформить рисунок, записав действия рисующего пользователя, могли бы скопировать этот рисунок в другое место. Следующий код заставит ваш апплет думать, что мышь передвинулась в новое положение:

Event FakeEvt;

long time = System.currentTimeMillis(); FakeEvt = new

Event(this, time, MOUSE_MOVE, new_x, new_y, 0, 0, null); postEvent(FakeEvt);

 

Таблица 5-5. Конструкторы событий

Конструктор события

Описание

Event(Object,long,int,

Пункт назначения, время, тип события, x-координата, y-

int,int,int,int,Object)

координата, идентификатор клавиши, модификаторы и параметр.

Event(Object, long, int, int, int,

Пункт назначения, время, тип события, x-координата, y-

int, int)

координата, идентификатор клавиши и модификаторы.

Event(Object, int, Object)

Пункт назначения, тип события и параметр.

Полный список типов событий приведен в табл. 5-6. Типы, отмеченные звездочкой, не относятся к апплетам. Типы SCROLL_ и LIST_ используются вместе с компонентами ввода пользователя, о которых мы будем говорить в главе 7, "Пользовательский интерфейс".

Таблица 5-6. События, отмеченные звездочкой, не используются в апплетах

Типы событий

+

WINDOW_DESTROY

MOUSE_EXIT

WINDOW_EXPOSE

SCROLL_LINE_UP

WINDOW_ICONIFY

SCROLL_LINE_DOWN

WINDOW_DEICONIFY SCROLL_PAGE_UP

W lNDOW_MOVED

SCROLL_PAGE_DOWN

KEY_PRESS

SCROLL_PAGE_ABSOLUTE

KEY_RELEASE

LIST_SELEGT

KEY_ACTION

LIST_DESELECT

KEY_ACTION_RELEASE ACTION_EVENT

MOUSE_DOWN

LOAD_FI LE *

MOUSE_UP

SAVE_FILE*

MOUSE_MOVE

GOT_FOCUS

MOUSE_DRAG

LOST_FOCUS

MOUSE_ENTER

 

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

www.books-shop.com

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

Анимация при помощи потоков

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

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

Рис. 5.6.

Вы можете использовать потоки в своих программах на Java, определяя расширения класса Thread. Подробно этот процесс описан в главе 11, "Многопотоковость". Но уже теперь мы объясним, как позволить апплету использовать потоки. Эта методика будет полезна, если, например, апплет отображает движущееся изображение, но вы хотите, чтобы апплет делал и другие полезные вещи во время проигрывания мультипликации.

Интерфейс Runnable

Как обсуждалось в главе 3, "Объектная ориентация в Java", язык Java не учитывает многократное наследование. Апплет не может непосредственно расширять ни класс Applet, ни класс Thread. Вы можете задать метод, который выполняется внутри потока, c помощью интерфейса Runnable. Этот интерфейс, подобно классу Thread, является частью пакета java.lang. Можно выбрать такой путь:

class ThreadedApplet extends Applet implements Runnable {

}

Интерфейс Runnable сообщает компилятору, что этот класс определит метод, который должен быть выполнен внутри потока. Выполняемый метод управляется экземпляром класса Thread, созданного непосредственно в апплете. Посмотрим, как можно использовать это на практике:

class ThreadedApplet extends Applet Implements Runnable { private Thread engine = null;

www.books-shop.com

// наш поток

public void init() {

engine = new Thread(this);

// этот поток теперь управляет нашим методом

}

public void start() { englne.start();

// метод начинает выполняться

}

public void stop() {

if (engine!=null && engine.isAlive()) { // если нужно,

engine.stop();

// останавливаем выполнение метода

}

}

public void run() {

while (engine.isAlive()) {

// код, расположенный здесь, выполняется, пока апплет не

остановится

}

}

}

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

Простые методы для работы с потоками

Класс Thread определяет множество методов для управления потоками. Мы подробно обсудим их в главе 11, "Многопотоковость". В табл. 5-7 кратко описаны наиболее важные методы, которые надо запомнить.

 

Таблица 5-7. Общие методы класса Thread

Метод

Описание

isAlive() Возвращает булевскую переменную, указывающую, является ли поток действующим или нет.

sleep(long) Просит, чтобы поток бездействовал определенное число миллисекунд. Этот метод выполнит InterruptedException, если получит сигнал прерывания от другого потока. (О прерываниях см. главу 11, "Многопотоковость".)

start() Начинает выполнение потока. stop() Останавливает выполнение потока.

suspend() Временно приостанавливает поток.

resume() Продолжает выполнение потока после приостановки.

Используя потоки, мы можем расширить апплет отображения курсора (пример 5-5), показанный на рис. 5-7, чтобы отобразить движущееся изображение, которое следует за курсором. Мы изменим апплет так, чтобы дать пользователю некоторый контроль над ним: нажатием кнопки мыши останавливать и снова запускать мультипликацию.

www.books-shop.com

Рис. 5.7.

Пример 5-6a. Анимированный апплет отображения курсора. import java.applet.*;

import java.awt.*; import java.net.*;

//Этот класс имеет метод, выполняемый внутри потока,

//и будет управляться через

//экземпляр потока, созданный в этом апплете. public class AnimatedCursorApplet extends Applet implements Runnable {

private int mouse_x, mouse_y;

//массив кадров мультипликации private Image CursorImages[];

//индекс текущего изображения private int CursorIndex = 0;

//поток управления выполнением метода private Thread anim = null;

//мультипликация не приостанавливается private boolean paused = false;

public void init() { resize(250,250);

//привяжем экземпляр потока к апплету anim = new Thread(this);

mouse_x = 125; mouse_y = 125;

//для начала принимаем 5 изображений

CursorImages = new Image[5]; int i;

String CursorParam; URL CursorURL;

//заполним массив изображений

for (i=0; i<5; i++) {

CursorParam = getParameter("CURSORFILE"+i); try {

CursorURL = new URL(CursorParam); CursorImages[i] = getImage(CursorURL);

}

catch (MalformedURLException e) {

// создаем пустой кадр, если URL неверен

CursorImages[i] = createImage(0,0);

}

www.books-shop.com

}

}

public void start() {

// начало выполнения метода anim.start();

}

public void stop() {

if (anim!=null && anim.isAlive()) {

// остановка выполнения метода при необходимости anim.stop();

}

}

public void paint(Graphics g) { int px, py;

//курсор указывает на текущее изображение

Image Cursor = CursorImages[CursorIndex]; g.drawRect(0,0,249,249);

//центрируем изображение

px = mouse_x - Cursor.getWidth(this)/2; py = mouse_y - Cursor.getHeight(this)/2; g.drawImage(Cursor,px,py,this);

}

public boolean mouseMove(Event evt, int x, int y) { mouse_x = x;

mouse_y = y; return true;

}

public boolean mouseDown(Event evt, int x, int y) {

//если метод был приостановлен, перезапустим его if (paused) {

anim.resume(); paused = false;

}

//иначе приостановим выполняемый метод

else {

anim.suspend(); paused = true;

}

return true;

}

public void run() {

while (anim!=null) { try {

// приостановка на 50 миллисекунд anim.sleep(50);

}

//если что-нибудь произошло catch (InterruptedException e) {}

//двигаемся к следующему изображению

CursorIndex = CursorIndex + 1; if (CursorIndex==5) {

//повторим снова с самого начала

CursorIndex = 0;

}

repaint();

}

}

}

Пример 5-6a. Web-страница для анимированного апплета отображения курсора.

<HTML><HEAD>

<TITLE>Animated Cursor Applet</TITLE> </HEAD>

<BODY>

www.books-shop.com