Druzya.org
Возьмемся за руки, Друзья...
 
 
Наши Друзья

Александр Градский
Мемориальный сайт Дольфи. 
				  Светлой памяти детей,
				  погибших  1 июня 2001 года, 
				  а также всем жертвам теракта возле 
				 Тель-Авивского Дельфинариума посвящается...

 
liveinternet.ru: показано количество просмотров и посетителей

Библиотека :: Компьютеры и Программирование :: А.В. Фролов, Г.В. Фролов - Microsoft Visual J++
 [Весь Текст]
Страница: из 275
 <<-
 
Создание приложений и аплетов на языке Java
Часть 1
   
   
   
   
   
АННОТАЦИЯ
   Книга представляет собой первую часть практического пособия по созданию 
автономных приложений и аплетов, работающих под управлением навигаторов WWW, на 
языке программирования Java. 
   Описаны основные отличия языка программирования Java от С++, среда 
выполнения приложений Java, приемы работы в интегрированной среде Microsoft 
Visual J++, основные библиотеки классов Java и методики их использования с 
иллюстрацией на примерах конкретных приложений. Читатель научится создавать 
сложные интерактивные аплеты для страниц серверов WWW.
   Эта книга будет полезна всем, кто начинает самостоятельно осваивать новый 
язык программирования Java, и может быть использована в качестве учебного 
пособия для студентов учебных заведений.
ВВЕДЕНИЕ
   Казалось бы, на сегодняшний день изобрели уже все языки программирования, 
какие только можно придумать. Но нет - появился еще один, с названием Java. 
Этот язык сумел завоевать весьма заметную популярность за последние несколько 
лет, так как он ориентирован на самую популярную компьютерную среду - сеть 
Internet и серверы WWW.
   Язык Java произошел от языка программирования Oak (а не от С++, как думают 
многие). Oak был приспособлен для работы в Internet и затем переименован в Java.
 Изучая Java, вы будете приятно удивлены тем, что его синтаксис близок к 
синтаксису языка C++. Унаследовав самое лучшее от языка программирования C++, 
язык Java при этом избавился от некоторых недостатков С++, в результате чего на 
нем стало проще программировать.  В этом языке нет, например, указателей, 
которые сложны в использовании и потенциально могут послужить причиной доступа 
программы к не принадлежащей ей области памяти. Нет множественного наследования 
и шаблонов, хотя функциональные возможности языка Java от этого не пострадали. 
Если вы умеете программировать на C++, для вас не составит особого труда 
изучить язык Java.
   Огромное преимущество Java заключается в том, что на этом языке можно 
создавать приложения, способные работать на различных платформах. К сети 
Internet подключены компьютеры самых разных типов - совместимые с IBM PC, 
компьютеры фирмы Apple, рабочие станции Sun и так далее. Даже в рамках 
компьютеров, созданных на базе процессоров Intel, существует несколько платформ,
 например, Microsoft Windows версии 3.1, Microsoft Windows 95,. Microsoft 
Windows NT, IBM OS/2, Solaris, различные разновидности операционной системы 
UNIX с графической оболочкой X-Windows. Между тем, создавая сервер WWW в сети 
Internet, вы бы наверняка хотели, чтобы им могло пользоваться как можно большее 
число людей. В этом случае вас выручат мультиплатформные приложения Java, не 
зависящие от конкретного типа процессора и операционной системы.
   Программы, составленные на языке программирования Java, можно разделить по 
своему назначению на две большие группы.
   К первой группе относятся приложения Java, предназначенные для автономной 
работы под управлением специальной интерпретирующей машины Java. Реализации 
этой машины созданы для всех основных компьютерных платформ.
   Вторая группа - это так называемые аплеты (applets). Аплеты представляют 
собой разновидность приложений Java, которые интерпретируются виртуальной 
машиной Java, встроенной в навигаторы WWW, такие как Microsoft Internet 
Explorer или Netscape Navigator. 
   Приложения, относящиеся к первой группе (в нашей книге мы будем называть их 
просто приложениями Java), это обычные автономные программы. Так как они не 
содержат машинного кода и работают под управлением специального интерпретатора, 
их производительность заметно ниже, чем у обычных программ, составленных, 
например, на языке программирования C++. Однако не следует забывать, что 
программы Java без перетрансляции способны работать на любой платформе, что 
само по себе имеет большое значение в плане разработок для Internet.
   Аплеты Java встраиваются в документы HTML, хранящиеся на сервере WWW. С 
помощью аплетов вы можете сделать страницы сервера WWW динамичными и 
интерактивными. Аплеты позволяют выполнять сложную локальную обработку данных, 
полученных от сервера WWW и введенных пользователем с клавиатуры. Из 
соображений безопасности аплеты (в отличие от обычных приложений Java) не имеют 
никакого доступа к файловой системе локального компьютера. Все данные для 
обработки они могут получить только от сервера WWW. Более сложную обработку 
данных можно выполнять, организовав взаимодействие между аплетами и 
расширениями сервера WWW - приложениями CGI и ISAPI.
   Для повышения производительности приложений Java в навигаторе Microsoft 
Internet Explorer использована технология с названием Just-in-Time Compilation, 
или JIT. При первой загрузке аплета его код транслируется в обычную исполнимую 
программу, которая сохраняется на диске и запускается. В результате общая 
скорость выполнения аплета Java увеличивается в несколько раз.
   Язык Java является объектно-ориентированным и поставляется с достаточно 
объемной библиотекой классов. Так же как и библиотеки классов систем разработки 
приложений на языке С++, такие как Microsoft Foundation Classes (MFC), 
библиотеки классов Java значительно упрощают разработку приложений, представляя 
в распоряжение программиста мощные средства решения распространенных задач. 
Поэтому программист может больше внимания уделить решению прикладных задач, а 
не таких, как, например, организация динамических массивов, взаимодействие с 
операционной системой или реализация элементов пользовательского интерфейса.
   Первоначально средства разработки приложений и аплетов Java были созданы 
фирмой Sun и до сих пор эти средства пользуются популярностью. В сети Internet 
по адресу http://www.sun.com есть сервер фирмы Sun, с которого можно бесплатно 
получить набор Java Development Kit (JDK). В JDK входят пакетные программы для 
компиляции исходных текстов приложений Java, виртуальная машина, программа 
автоматизированного создания документации по классам, справочник по классам 
Java и другие необходимые средства.
   Для тех, кто привык пользоваться средствами разработки корпорации Microsoft, 
мы можем порекомендовать SDK-Java - пакетное средство разработки приложений и 
аплетов Java, расположенное на сервере http://microsoft.com. В составе этого 
средства есть также комплект документации по классам Java в виде набора 
документов HTML. Вы можете переписать себе SDK-Java бесплатно с указанного 
сревера Microsoft.
   Если вы привыкли к интегрированным средствам разработки приложений, таким 
как Microsoft Visual C++, инструментарий JDK и SDK-Java могут показаться вам 
неудобными. В этом случае вы можете воспользоваться такими системами разработки 
приложений Java, как, например, Symantec Cafe или Microsoft Visual J++.
   Мы рассмотрим недорогое, но удобное и мощное интегрированное средство 
разработки приложений Microsoft Visual J++. Оценочную версию этого средства 
можно бесплатно получить с сервера WWW корпорации Microsoft. Заметим, что эта 
оценочная версия не содержит внутри себя “бомбы”, уничтожающей программу через 
заданное время, и вы сможете оценивать ее достаточно долго. Стоимость 
коммерческой версии Microsoft Visual J++ невелика, особенно с учетом того, что 
в коробке вы найдете прекрасную книгу Стефана Дэвиса “Learn Java Now” (на 
английском языке) издательства Microsoft Press. Если вы работаете с Microsoft 
Visual C++, то после установки Microsoft Visual J++ вы получите единую среду 
для разработки на языках программирования C++ и Java, что очень удобно.
   На прилавках книжных магазинов вы можете найти несколько переводных книг, 
посвященных программированию на языке Java. Практически все они ориентированы 
на инструментарий JDK, созданный фирмой Sun, и содержат более или менее 
подробное описание классов Java. В нашей книге мы научим вас работать с 
Microsoft Visual J++ и приведем все сведения, необходимые для разработки как 
автономных приложений, так и аплетов Java.
   При изложении материала мы будем предполагать, что вы знакомы с языком 
программирования C++. Наша книга не является учебником по языку Java. Мы будем 
рассказывать о том, как использовать язык Java и систему разработки Microsoft 
Visual J++ для создания автономных приложений и аплетов Java, как организовать 
взаимодействие аплетов с сервером WWW (во второй части этой книги). При этом мы 
будем считать, что с основами языка Java вы знакомы.
   Что вам потребуется для работы с книгой?
   Очень хорошо, если вы подключены к сети Internet или имеете возможность хотя 
бы эпизодической работы в этой сети. В этом случае вам будет доступна 
бесплатная оценочная версия Microsoft Visual J++, другие бесплатные средства 
разработки приложений и аплетов Java, грандиозные запасы документации и 
примеров программ.
   Для проверки работы аплетов вам следует установить навигатор Internet, 
способный запускать аплеты Java. Это Microsoft Internet Explorer версии 3.01 и 
Netscape Navigator версии 3.0. Заметим, что для запуска аплетов вам не нужно 
обязательно подключаться к Internet - вы можете встраивать аплеты в документы 
HTML, расположенные на локальном диске вашего компьютера и просматривать эти 
документы навигатором просто как локальные файлы. 
   Автономные приложения Java работают под управлением специального 
интерпретатора (виртуальной машины Java), поэтому для их отладки вам также не 
потребуется сеть Internet.
   Если вы собираетесь проверять работу приложений и аплетов Java, 
взаимодействующих с сервером WWW, вы можете воспользоваться собственным 
сервером в Internet или в корпоративной сети Intranet (если они у вас есть). 
Примеры таких аплетов мы приведем в следующей книге “Библиотеки системного 
программиста”, посвященной Java. Можно также установить сервер WWW, входящий в 
комплект операционной системы Microsoft Windows NT Workstation версии 4.0, или 
Personal Web Service для операционной системы Microsoft Windows 95. Последний 
доступен для бесплатной загрузки с сервера http://microsoft.com.
   Для того чтобы успешно работать с аплетами Java, вы должны иметь некоторое 
представление о серверах WWW и языке гипертекстовой разметки документов HTML. 
Поэтому перед чтением этой книги мы рекомендуем вам ознакомится с 29 томом 
“Библиотеки системного программиста”, который называется “Сервер Web своими 
руками”. В этой книге мы рассказали о том, как сделать собственный сервер WWW в 
сети Internet и Intranet, а также привели необходимую информацию о расширениях 
сервера WWW, реализованных как приложения CGI и ISAPI.
   Базовые знания об Internet, а также описание конкретных способов подключения 
к ней в российских условиях вы найдете в 23 томе “Библиотеки системного 
программиста”, который называется “Глобальные сети компьютеров. Практическое 
введение в Internet, E-Mail, FTP, WWW и HTML, программирование для Windows 
Sockets”. Мы рекомендуем эту книгу для тех, кто только планирует подключиться к 
Internet, но еще не вполне осознает, как это можно сделать и для чего это нужно.

   Что еще почитать?
   Количество книг, посвященных Java, растет катастрофически, особенно за 
рубежом. Среди удачных еще раз назовем книгу Стефана Дэвиса с названием “Learn 
Java Now”, которая может служить учебником по языку Java для тех, кто никогда 
не программировал на С и С++.
   Среди переводных книг, которые можно встретить в продаже, отметим книгу 
Джона Родли “Создание JAVA-апплетов”. Эта книга рассчитана на серьезных 
программистов, хорошо знающих язык программирования Java. Однако для тех, кто 
только начинает изучать язык Java, она может оказаться слишком сложной.
   Другая книга, заслуживающая внимание, это книга Криса Джамса  с названием 
“Java”. После небольшого введения, рассчитанного на начинающих, в этой книге 
приводится описание более чем дюжины достаточно интересных аплетов с исходными 
текстами и комментариями.
   В качестве справочника по языку Java и библиотекам классов вы можете 
использовать книгу И. Баженовой “Язык программирования Java”, которая вышла в 
издательстве АО “Диалог -МИФИ”.
   Ну и, конечно, вам следует ознакомиться с различными руководствами по языку 
Java, хранящимися в сети Internet. В качестве отправной точки для поиска вы 
можете выбрать сервер основного разработчика этого языка - фирмы Sun. Адрес ее 
сервера мы уже приводили: http://www.sun.com. Помимо документации и примеров 
программ на Java, здесь вы найдете ссылки на другие ресурсы, посвященные этому 
языку программирования. Попробуйте также воспользоваться поисковыми серверами, 
такими как Jahoo! и Alta Vista, указав в качестве ключевого слово “Java”. 
БЛАГОДАРНОСТИ
   В работе над книгой нам помогали сотрудники фирмы Interactive Products Inc. 
Максим Синев и Сергей Ноженко, у которых мы консультировались по различным 
вопросам.
   Мы признательны генеральному директору АО “ДиалогНаука” Антимонову Сергею 
Григорьевичу и его заместителю Лященко Юрию Павловичу за возможность размещения 
информации о наших книгах на сервере Web по адресу http://www.dials.ccas.
ru/frolov, а также за возможность доступа к сети Internet через сервер АО 
“ДиалогНаука”.
   Мы также благодарим корректора Кустова В. С. и сотрудников издательского 
отдела АО “Диалог-МИФИ” Голубева О. А., Голубева А. О., Дмитриеву Н. В., 
Виноградову Е. К., Кузьминову О. А.
КАК СВЯЗАТЬСЯ С АВТОРАМИ
   Полную информацию о всех наших книгах серий “Библиотека системного 
программиста” и “Персональный компьютер. Шаг за шагом”, а также дискеты к 
книгам, статьи и другую информацию вы можете найти в сети Internet на серверах 
Web по следующим адресам:
http://www.glasnet.ru/~frolov
http://www.dials.ccas.ru/frolov
   Вы можете передать нам свои замечания и предложения по содержанию этой и 
других наших книг через электронную почту по адресам:
[email protected]
[email protected]
   Если электронная почта вам недоступна, присылайте ваши отзывы в АО 
“Диалог-МИФИ” по адресу:
115409, Москва, ул. Москворечье, 31, корп. 2, 
тел. 324-43-77
   Приносим свои извинения за то что не можем ответить на каждое письмо. Мы 
также не занимаемся продажей и рассылкой книг, дискет, рекламы, отдельных 
фрагментов наших книг и исходных текстов к книгам, консультациями через 
электронную почту. По вопросам приобретения книг и дискет обращайтесь 
непосредственно в издательство “Диалог-МИФИ”.
1 НОВЫЙ ЯЗЫК ПРОГРАММИРОВАНИЯ
   Почему вам нужно изучать новый язык программирования Java?
   Если ответить на этот вопрос кратко, то потому, что он специально 
ориентирован на самые передовые технологии, связанные с сетью Internet. 
Растущая популярность Internet и, в особенности, серверов WWW, создает для 
программистов новые возможности для реализации своих способностей. Затратив 
всего пару-тройку недель на чтение нашей книги, вы сможете проникнуться 
идеологией Java и, как мы надеемся, сумеете по достоинству оценить возможности 
этого языка программирования.
   В этой главе мы расскажем об основных особенностях Java и библиотеках 
классов, которые поставляются в составе систем разработки приложений. После 
этого вы поймете, почему этот язык программирования выглядит весьма 
привлекательно именно для реализации сетевых проектов в Internet и в 
корпоративных сетях Intranet.
   Мобильность Java
   В свое время вы слышали, что язык программирования С является мобильным в 
том смысле, что имеется принципиальная возможность переноса программ, 
составленных на этом языке, на различные платформы. 
   Однако следует отметить, что создание действительно многоплатформных 
приложений - непростая задача. К сожалению, дело не ограничивается 
необходимостью перекомпиляции исходного текста программы для работы в другой 
среде. Много проблем возникает с несовместимостью программных интерфейсов 
различных операционных систем и графических оболочек, реализующих 
пользовательский интерфейс.
   Вспомните хотя бы проблемы, связанные с переносом 16-разрядных приложений 
Windows в 32-разрядную среду Microsoft Windows 95 и Microsoft Windows NT. Даже 
если вы тщательно следовали всем рекомендациям Microsoft, разрабатывая 
приложения так, чтобы они могли работать в будующих версиях Windows, едва ли 
вам удасться просто перекомпилировать исходные тексты, не изменив в них ни 
строчки. Ситуация еще больше ухудшается, если вам нужно, например, перенести 
исходные тексты приложения Windows в среду операционной системы IBM OS/2 или в 
оболочку X-Windows операционной системы UNIX. А ведь есть еще компьютеры Apple, 
рабочие станции с процессором RISC и другие!
    Как нетрудно заметить, даже если стандартизовать язык программирования для 
всех платформ, проблемы совместимости с программным интерфейсом операционной 
системы значительно усложняют перенос программ на различные платформы. И, 
конечно, вы не можете мечтать о том, чтобы загрузочный модуль одной и той же 
программы мог работать без изменений в среде различных операционных систем и на 
различных платформах. Если программа подготовлена для процессора Intel, она ни 
за что не согласится работать на процессоре Alpha или каком-либо другом.
   В результате создавая приложение, способное работать на различных платформах,
 вы вынуждены фактически создавать несколько различных приложений и 
сопровождать их по отдельности.
   На рис. 1.1 мы показали, как приложение, изначально разработанное для 
Microsoft Windows NT, переносится на платформу Apple Macintosh.

Рис. 1.1. Перенос приложения с платформы Windows NT на платформу Apple 
Macintosh
   Вначале программист готовит исходные тексты приложения для платформы 
Microsoft Windows NT и отлаживает их там. Для получения загрузочного модуля 
исходные тексты компилируются и редактируются. Полученный в результате 
загрузочный модуль может работать на процессоре фирмы Intel в среде 
операционной системы Microsoft Windows NT.
   Для того чтобы перенести приложение в среду операционной системы компьютера 
Apple Macintosh, программист вносит необходимые изменения в исходные тексты 
приложения. Эти изменения необходимы из-за различий в программном интерфейсе 
операционной системы Microsoft Windows NT и операционной системы, установленной 
в Apple Macintosh. Далее эти исходные тексты транслируются и редактируются, в 
результате чего получается загрузочный модуль, способный работать в среде Apple 
Macintosh, но не способный работать в среде Microsoft Windows NT.
   Программа на языке Java компилируется в двоичный модуль, состоящий из команд 
виртуального процессора Java. На настоящий момент созданы только первые модели 
физического процессора, способного выполнять эти двоичные команды, однако 
интерпретаторы Java имеются на всех основных компьютерных платформах. 
Разумеется, на каждой платформе используется свой интерпретатор, или, точнее 
говоря, свой виртуальный процессор Java.
   Если ваше приложение Java (или аплет) должно работать на нескольких 
платформах, нет необходимости компилировать его исходные тексты несколько раз. 
Вы можете откомпилировать и отладить приложение Java на одной, наиболее удобной 
для вас платформе. В результате вы получите двоичный модуль, пригодный для 
любой платформы, где есть виртуальный процессор Java.
   Сказанное иллюстрируется на рис. 1.2.

Рис. 1.2. Подготовка приложения Java для работы на разных платформах
   Таким образом, приложение Java компилируется и отлаживается только один раз, 
что уже значительно лучше. Остается, правда, вопрос - как быть с программным 
интерфейсом операционной системы, который отличается для разных платформ?
   Здесь разработчиками Java предлагается достаточно неплохое решение. 
Приложение Java не обращается напрямую к интерфейсу операционной системы. 
Вместо этого оно пользуется готовыми стандартными библиотеками классов, 
содержащими все необходимое для организации пользовательского интерфейса, 
обращения к файлам, для работы в сети и так далее.
   Внутренняя реализация библиотек классов, разумеется, зависит от платформы. 
Однако все загрузочные модули, реализующие возможности этих библиотек, 
поставляются в готовом виде вместе с виртуальной машиной Java, поэтому 
программисту не нужно об этом заботиться. Для операционной системы Microsoft 
Windows, например, поставляются библиотеки динамической загрузки DLL, внутри 
которых запрятана вся функциональность стандартных классов Java.
   Абстрагируясь от аппаратуры на уровне библиотек классов, программисты могут 
больше не заботиться о различиях в реализации программного интерфейса 
конкретных операционных систем. Это позволяет создавать по-настоящему мобильные 
приложения, не требующие при переносе на различные платформы перетрансляции и 
изменения исходного текста.
   Еще одна проблема, возникающая при переносе программ, составленных на языке 
программирования С, заключается в том, что размер области памяти, занимаемой 
переменными стандартных типов, различный на разных платформах. Например, в 
среде операционной системы Microsoft Windows версии 3.1 переменная типа int в 
программе, составленной на С, занимает 16 бит. В среде Microsoft Windows NT 
этот размер составляет 32 бита.
   Очевидно, что трудно составлять программу, не зная точно, сколько имеется 
бит в слове или в байте. При переносе программ на платформы с иной разрядностью 
могут возникать ошибки, которые трудно обнаружить.
   В языке Java все базовые типы данных имеют фиксированную разрядность, 
которая не зависит от платформы. Поэтому программисты всегда знают размеры 
переменных в своей программе.
   Базовые типы данных
   В языке Java определено восемь базовых типов данных, перечисленных ниже:
   
Тип данныхРазмер занимаемой области памятиЗначение по умолчаниюboolean8false
byte80char16‘x0’short160int320long640float320.0Fdouble640.0D   
   Для каждого базового типа данных отводится конкретный размер памяти, который,
 как мы говорили в предыдущем разделе, не зависит от платформы, на которой 
выполняется приложение Java. Фактический размеры памяти, отведенные для 
хранения переменной, могут отличаться от приведенных выше, например, для 
хранения переменной типа short может быть зарезервировано слово размером 32 
бита. Однако язык Java сделан таким образом, что это никак не повлияет на 
мобильность приложения. Забегая вперед, скажем, что в языке Java нет указателей,
 поэтому вы не можете адресоваться к элементам массива чисел по относительному 
смещению этих элементов в оперативной памяти. Следовательно, точный размер 
элемента в данном случае не играет никакой роли.
   Все базовые типы данных по умолчанию инициализируются, поэтому программисту 
не нужно об этом беспокоиться. Вы можете также инициализировать переменные 
базовых типов в программе или при их определении, как это показано ниже:
int nCounter = 0;
int i;
i = 8;
   Переменные типа boolean могут находиться только в двух состояниях - true и 
false, причем эти состояния никаким образом нельзя соотнести с целыми 
значениями. Вы не можете, как это было в языке С, выполнить преобразование типа 
boolean, например, к типу int - компилятор выдаст сообщение об ошибке.
   Переменная типа byte занимает восемь бит памяти и про нее больше нечего 
сказать. 
   Что же касается типа char, то он используется для хранения символов в 
кодировке Unicode. Эта кодировка позволяет хранить национальные наборы символов,
 что очень удобно для интернациональных приложений, предназначенных для работы 
в Internet.
   Переменные типа byte, short, int и long являются знаковыми. В языке Java нет 
беззнаковых переменных, как это было в языке С.
   Приложение Java может оперировать числами в формате с плавающей точкой, 
определенным в спецификации IEEE 754. Тип float позволяет хранить числа с 
одинарной точностью, а формат double - с двойной.
   Переменные базовых типов могут передаваться функциям в качестве параметров 
только по значению, но не по ссылке. Поэтому следующий фрагмент кода работать 
не будет:
int x;
void ChangeX(int x)
{
  x = 5;
}
. . .
x = 0;
ChangeX(x);
   После вызова функции ChangeX содержимое переменной x останется равным нулю.
   Проблему можно решить, если вместо базовых переменных использовать объекты 
встроенных классов, соответствующие базовым переменным. О встроенных классах вы 
узнаете из следующего раздела.
   Библиотеки классов Java
   Если предоставить в распоряжение программиста только язык программирования и 
не снабдить его набором готовых модулей, предназначенных для решения самых 
распространенных задач, ему придется отвлекаться на множество мелких деталей. 
Обычно все профессиональные системы разработки приложений содержат в своем 
составе набор стандартных библиотечных функций или библиотеки классов, таких 
как Microsoft MFC или Borland OWL. В комплекте со всеми средствами разработки 
Java поставляются достаточно развитые библиотеки классов, значительно 
упрощающие программирование. В этом разделе мы кратко расскажем о составе и 
назначении библиотек классов Java.
   Встроенные классы
   В языке Java все классы происходят от класса Object, и, соответственно, 
наследуют методы этого класса. Некоторые библиотеки классов подключаются 
автоматически, и мы будем называть их встроенными. К таким относится, в 
частности, библиотека с названием java.lang. Другие библиотеки классов вы 
должны подключать в исходном тексте приложения Java явным образом с помощью 
оператора import, о котором мы еще расскажем.
   Замещающие классы
   Очень часто в наших приложениях вместо базовых типов переменных мы будем 
использовать объекты встроенных классов, которые называются замещающими 
классами (wrapper classes). Ниже мы перечислили названия этих классов и 
названия базовых типов данных, которые они замещают:
   
Базовый тип данныхЗамещающий классbooleanBooleancharCharacterintIntegerlongLong
floatFloatdoubleDouble   
   Заметим, что для преобразования базовых типов данных в объекты замещающего 
класса и обратно вы не можете применять оператор присваивания. Вместо этого 
необходимо использовать соответствующие методы замещающих классов, которые мы 
будем рассматривать по ходу дела. Полную информацию о классах и методах вы 
сможете найти в справочной системе, которая устанавливается вместе с Microsoft 
Visual J++, поэтому в нашей книге вы не найдете справочника по классам Java.
   Класс String
   Класс String предназначен для работы с такими часто встречающимися объектами,
 как текстовые строки. Методы этого класса позволяют выполнять над строками 
практически все операции, которые вы делали раньше при помощи библиотечных 
функций C. Это преобразование строки в число и обратно с любым заданным 
основанием, определение длины строки, сравнение строк, извлечение подстроки и 
так далее.
   Хотя в языке Java не допускается перезагрузка (переопределение) операторов, 
для объектов класса Stirng и объектов всех произошедших от него классов сделана 
встроенная перезагрузка операторов “+” и “+=”. С помощью этих операторов можно 
выполнять слияние текстовых строк, например:
System.out.println(“x = “ + x + ‘\n’);
   Здесь в качестве параметра функции println передается текстовая строка, 
составленная из трех компонент: строки “x = “, числа x и символа перехода на 
следующую строку ‘\n’. Значение переменной x автоматически преобразуется в 
текстовую строку (что выполняется только для текстовых строк) и полученная 
таким образом текстовая строка сливается со строкой “x = “.
   Другие встроенные классы
   Среди других встроенных классов отметим класс Math, предназначенный для 
выполнения математических операций, таких как взятие синуса, косинуса и 
тангенса. 
   Предусмотрены также классы для выполнения запуска процессов и задач, 
управления системой безопасности, а также для решения прочих системных задач.
   Библиотека встроенных классов содержит очень важные классы для работы с 
исключениями. Эти классы нужны для обработки ошибочных ситуаций, которые могут 
возникнуть (и возникают!) при работе приложений или аплетов Java.
   Подключаемые библиотеки классов
   Ниже мы кратко перечислим подлкючаемые библиотеки классов для того чтобы вы 
могли оценить возможности набора классов Java. Подробное описание этих классов 
есть в справочной системе Microsoft Visual J++ и в литературе, список которой 
приведен в конце книги. Мы же ограничимся описанием тех классов, которые будем 
использовать в наших примерах приложений.
   Библиотека классов java.util
   Библиотека классов java.util очень полезна при составлении приложений, так 
как в ней имеются классы для создания таких структур, как динамические массивы, 
стеки и словари. Есть классы для работы с генератором псевдослучайных чисел, 
для разбора строк на составляющие элементы (токены), для работы с календарной 
датой и временем.
   Библиотека классов java.io
   В библиотеке классов java.io собраны классы, имеющие отношение к вводу и 
выводу данных через потоки. Заметим, что с использованием этих классов можно 
работать не только с потоками байт, но также и с потоками данных других типов, 
например числами int или текстовыми строками.
   Библиотека классов java.net
   Язык программирования Java разрабатывался в предположении, что им будут 
пользоваться для создания сетевых приложений. Поэтому было бы странно, если бы 
в составе среды разработки приложений Java не поставлялась библиотека классов 
для работы в сети. Библиотека классов java.net предназначена как раз для этого. 
Она содержит классы, с помощью которых можно работать с универсальными сетевыми 
адресами URL, передавать данные с использованием сокетов TCP и UDP, выполнять 
различные операции с адресами IP. Эта библиотека содержит также классы для 
выполнения преобразований двоичных данных в текстовый формат, что часто бывает 
необходимо.
   В качестве примера приложения, составленного на языке программирования Java 
и ориентированного на работу в сети Internet, можно привести игру Java Color 
Lines (рис. 1.3).

Рис. 1.3. Сетевая игра Java Color Lines, расположенная на сервере http://spektr.
orc.ru
   Это сетевая версия извстной игры Lines, которая выполнена в виде нескольких 
аплетов, взаимодействующих между собой и между сервером WWW с адресом 
http://spektr.orc.ru, на котором они расположены. Так как список имен игроков и 
достигнутых ими результатов хранится на сервере, вы можете поучаствовать в 
мировом турнире, сразившись с игроками из разных стран.
   В следующем томе “Библиотеки системного программиста”, посвященном Java, мы 
расскажем о том, как организовать взаимодействие между аплетами и сервером WWW.
   Библиотека классов java.awt
   Для создания пользовательского интерфейса аплеты Java могут и должны 
использовать библиотеку классов java.awt. AWT - это сокращение от Abstract 
Window Toolkit (инструментарий для работы с абстрактными окнами).
   Классы, входящие в состав библиотеки java.awt, предоставляют возможность 
создания пользовательского интерфейса способом, не зависящим от платформы, на 
которой выполняется аплет Java. Вы можете создавать обычные окна и диалоговые 
панели, кнопки, переключатели, списки, меню, полосы просмотра, однострочные и 
многострочные поля для ввода текстовой информации.
   Библиотека классов java.awt.image
   В среде любой операционной системы работа с графическими изображениями 
является достаточно сложной задачей. В 14 томе “Библиотеки системного 
программиста”, который называется “Графический интерфейс GDI в MS Windows” мы 
детально рассмотрели вопросы, связанные с рисованием графики и обработкой 
графических файлов в среде операционной системы Microsoft Windows. Если вы 
будете рисовать графические изображения в среде IBM OS/2 или X-Windows, вам, 
очевидно, придется использовать другие методики и другой программный интерфейс. 
Большую сложность также вызывает разбор заголовков графических файлов, так как 
они могут иметь различный формат и иногда содержат неправильную или 
противоречивую информацию.
   Когда вы программируете на Java, рисование и обработка графических 
изображений выполняется намного проще, так как вам доступна специально 
предназначенная для этого библиотека классов java.awt.image. Помимо широкого 
разнообразия и удобства определенных в ней классов и методов, отметим 
способность этой библиотеки работать с графическими изображениями в формате GIF.
 Этот формат широко используется в Internet, так как он позволяет сжимать файлы 
графических изображений во много раз без потери качества за счет устранения 
избыточности.
   Библиотека классов java.awt.peer
   Библиотека классов java.awt.peer служит для подключения компонент AWT 
(например, кнопок, списков, полей редактирования текстовой информации, 
переключателей и так далее) к реализациям, зависящим от платформы, в процессе 
создания этих компонент.
   Библиотека классов java.applet
   Как нетрудно догадаться из названия, библиотека классов java.applet 
инкапсулирует поведение аплетов Java. Когда вы будете создавать свои аплеты, 
вам будет нужен класс Applet, расположенный в этой библиотеке классов. 
Дополнительно в библиотеке классов java.applet определены интерфейсы для 
подключения аплетов к содержащим их документам и классы для проигрывания 
аудиофрагментов.
   Указатели, которых нет
   Самая большая и шокирующая новость для тех, кто раньше программировал на С, 
а теперь занялся изучением Java, это то, что в языке Java нет указателей. 
Традиционно считалась, что работать с указателями трудно, а их использование 
приводит к повялению трудно обнаруживаемых ошибок. Поэтому разработчики Java 
решили отказаться от использования указателей совсем.
   Спешим успокоить - вы сможете успешно составлять приложения Java и без 
указателей, несмотря на то что вам, возможно, придется немного изменить стиль 
программирования.
   Вы можете спросить: как же передавать функциям ссылки на объекты, если нет 
указателей?
   Если вам нужно передать ссылку на переменную базового типа, такого, например,
 как int или long, то ничего не получится - мы уже говорили, что переменные 
базовых типов передаются по значению, а не по ссылке. Поэтому вы не сможете 
напрямую создать на языке Java эквивалент следующей программы, составленной на 
языке С:
// Некоторая переменная
int  nSomeValue;

// Функция, изменяющая значение переменной,
//   заданной своим адресом
void StoreValue(int *pVar, int nNewValue)
{
  pVar->nNewValue;
}
. . .
StoreValue(&nSomeValue, 10);
   Выход, однако, есть.
   Язык Java позволяет использовать вместо указателей ссылки на объекты. 
Пользуясь этими ссылками, вы можете адресовать объекты по их имени, вызывая 
методы и изменяя значения данных объектов. 
   Что же касается данных базовых типов, если вам нужно передавать на них 
ссылки, то следует заменить базовые типы на соответствующие встроенные классы. 
Например, вместо типа int используйте класс Integer, вместо типа long - класс 
Long и так далее. 
   Инициализация таких объектов должна выполняться с помощью конструктора, как 
это показано ниже:
Integer nSomeValue;
nSomeValue = new Integer(10);
   Первая строка создает неинициализированную ссылку с именем nSomeValue и 
типом Integer. При попытке использования такой ссылки возникнет исключение.
   Вторая строка создает объект класса Integer, вызывая конструктор. Этот 
конструктор определяет начальное значение. После выполнения оператора 
присваивания ссылка nSomeValue будет ссылаться на реальный объект класса 
Integer и ее можно будет использовать.
   Имя объекта nSomeValue типа Integer вы можете передавать функциям в качестве 
параметра, причем это будет ссылкой на объект.
   Составляя программы на языке С, вы часто использовали указатели для 
адресации элементов массивов, созданных статически или динамически в 
оперативной памяти. Зная начальный адрес такого массива и тип хранящихся в нем 
элементов, вы могли адресоваться к отдельным элементам массива.
   В языке Java реализован механизм массивов, исключающих необходимость 
использования указателей. 
   Массивы в Java
   Для создания массива вы можете пользоваться квадратными скобками, расположив 
их справа от имени массива или от типа объектов, из которых составлен массив, 
например:
int nNumbers[];
int[] nAnotherNumbers;
   Допустимы оба варианта, поэтому вы можете выбрать тот, который вам больше 
нравится.
   При определении массивов в языке Java нельзя указывать их размер. 
Приведенные выше две строки не вызывают резервирования памяти для массива. 
Здесь просто создаются ссылки на массивы, которые без инициализации 
использовать нельзя.
   Для того чтобы заказать память для массива, вы должны создать 
соответствующие объекты с помощью ключевого слова new, например:
int[] nAnotherNumbers;
nAnotherNumbers = new int[15];
   Как выполнить инициализацию ячеек таблицы?
   Такую инициализацию можно выполнить либо статически, либо динамически. В 
первом случае вы просто перечисляете значения в фигурных скобках, как это 
показано ниже:
int[] nColorRed = {255, 255, 100, 0, 10};
   Динамическая инициализация выполняется с использованием индекса массива, 
например, в цикле:
int nInitialValue = 7;
int[] nAnotherNumbers;
nAnotherNumbers = new int[15];
for(int i = 0; i < 15; i++)
{
  nAnotherNumbers[i] = nInitialValue;
}
   Вы можете создавать массивы не только из переменных базовых типов, но и из 
произвольных объектов. Каждый элемент такого массива должен инициализироваться 
оператором new.
   Массивы могут быть многомерными и, что интересно, несимметричными.
   Ниже создается массив массивов. В нулевом и первом элементе создается массив 
из четырех чисел, а во втором - из восьми:
int[][] nDim = new int[5][10];
nDim[0] = new int [4];
nDim[1] = new int [4];
nDim[2] = new int [8];
   Заметим, что во время выполнения приложения Java виртуальная машина Java 
проверяет выход за границы массива. Если приложение пытается выйти за границы 
массива, происходит исключение.
   Массивы в языке Java являются объектами некоторого встроенного класса. Для 
этого класса существует возможность определить размер массива, обратившись к 
элементу данных класса с именем length, например:
int[] nAnotherNumbers;
nAnotherNumbers = new int[15];
for(int i = 0; i < nAnotherNumbers.length; i++)
{
  nAnotherNumbers[i] = nInitialValue;
}
   Для определения размера массива вам не нужен такой оператор как sizeof из 
языка программирования С, так как существует другой способ определения размера 
массива.
   Сборка мусора
   Одна из интереснейших особенностей языка программирования Java и среды 
выполнения приложений Java заключается в наличии специального процесса сборки 
мусора, предназначенного для удаления ненужных объектов из памяти. Эта система 
избавляет программиста от необходимости внимательно следить за использованием 
памяти, освобождая ненужные более области явным образом.
   Создавая объекты в Java, вы можете руководствоваться принципом “Создай и 
забудь”, так как система сборки мусора позаботится об удалении ваших объектов. 
Объект будет удален из памяти, как только на него не останется ни одной ссылки 
из других объектов.
   Приоритет процесса сборки мусора очень низкий, поэтому “уборка” среды 
выполнения приложений Java не отнимает ресурсы у самих приложений.
   Особенности реализации классов в Java
   Если вы умеете программировать на языке С++, у вас не возникнет никаких 
проблем с программированием на Java, так как эти языки очень похожи. Однако 
есть и некоторые отличия, которые следует учитывать. Мы приведем только краткое 
перечисление основных отличий. Более подробную информацию вы найдете в 
литературе, список которой есть в конце книги.
   Определение класса
   Для создания классов вы можете использовать только ключевое слово class. Что 
же касается union, то это ключевое слово в Java не применяется для создания 
классов.
   В языке программирования С++ описание класса может быть отделено от его 
определения. Для Java это не так - описание класса не допустимо. Все методы 
должны быть определены внутри определения класса.
   Недопустимо определение вложенных классов.
   В Java также нет шаблонов. Вы можете создавать классы только на базе других 
классов.
   Объект класса создается при помощи ключевого слова new, однако вы не можете 
удалить объект явным образом, так как ключевое слово delete языка 
программирования С++ в Java не используется.
   При определении класса вы не можете указать деструктор. Функции удаления 
объектов Java из памяти выполняет система сборки мусора.
   Внутри одного исходного файла вы можете определить только один общедоступный 
класс public.
   Все классы в Java наследуются от класса Object, поэтому для любого объекта 
вы можете использовать методы этого класса.
   Определение методов
   Вы не можете определять методы вне тела класса, создавая таким образом 
глобальные функции. Нет также возможности определения вне класса глобальных 
данных. Тем не менее, внутри класса можно определять статические методы и поля 
(с помощью ключевого слова static), которые будут играть роль глобальных 
методов и данных.
   Пользуясь ключевыми словами static и final, вы можете определять внутри 
классов глобальные константы.
   Если в базовом классе метод определен с ключевым словом final, его нельзя 
переопределить в дочернем классе, созданном на базе данного метода.
   Методы не могут быть определены как inline.
   Методы Java могут создавать исключения, вызванные возникновением ошибок или 
других событий. Все создаваемые исключения должны либо обрабатываться внутри 
метода, либо описываться в определении метода после ключевого слова throws.
   Переопределение операторов
   В языке С++ вы могли переопределить операторы, такие как +, -, ++ и так 
далее. Язык Java не допускает такое переопределение, что сделано для упрощения 
программирования. Тем не менее, операторы “+” и “+=” перегружены по умолчанию 
для выполнения операции слияния текстовых строк класса String.
   Интерфейсы
   Интерфейсы создаются при помощи ключевого слова interface таким же образом, 
что и классы. Однако в отличие от последних, интерфейсы являются аналогом 
абстрактных базовых классов без полей данных и предназначены только для 
определений набора методов для решения каких-либо задач, например, добавления 
компонент в контейнеры, организации списков, сортировки и так далее.
   Вы можете создать свой класс на базе другого класса, указав при этом с 
помощью ключевого слова implements, что он реализует тот или иной интерфейс. 
При этом наряду с методами базового класса в созданном таким образом классе 
будут доступны методы, определенные в интерфейсе.
   Ссылки на методы класса
   Так как в Java нет указателей, нет возможности ссылаться на методы с помощью 
оператора ->. Для ссылки на метод класса используется только оператор “точка”.
   Оператор “::” также не определен в Java. Если вам необходимо вызвать метод 
из базового класса, следует использовать ключевое слово super.
   Наследование
   С помощью ключевого слова extends вы можете унаследовать один класс 
(дочерний) от другого (базового).
   Множественное наследование не допускается. Таким образом, для каждого 
дочернего класса может быть только один базовый класс. При необходимости, 
однако, этот дочерний класс может реализовывать произвольное количество 
интерфейсов.
   Для ссылки на методы базового класса вы должны использовать ключевое слово 
super.
   При необходимости вы можете вызвать в первой исполняемой строке конструктора 
дочернего класса конструктор базового класса (опять же с помощью ключевого 
слова super).
2 ПЕРВОЕ ПРИЛОЖЕНИЕ И ПЕРВЫЙ АПЛЕТ
   Не стремясь быть оригинальными, начнем программирование на Java с 
составления простейшей программы, которая выводит текстовую строку “Hello, 
Java!”. Для этого вам сначала нужно установить среду разработки Microsoft 
Visual J++, запустив программу setup.exe, расположенную в корневом каталоге 
дистрибутивного компакт-диска.
   Процесс установки не вызывает затруднений. Вы просто должны следовать 
инструкциям, появляющимся на экране. Заметим только, что если у вас уже была 
установлена система Microsoft Visual C++, то имеет смысл для установки 
Microsoft Visual J++ использовать тот же каталог, что был использован для 
установки Microsoft Visual C++. При этом формируется единая среда разработки 
приложений на языках программирвоания C++ и Java, что очень удобно.
   Приложение Hello
   Как мы уже говорили, приложения Java могут выполняться под управлением 
специального интерпретатора, работающего в рамках отдельного процесса, либо под 
управлением навигатора Internet, такого как Microsoft Internet Explorer или 
Netscape Navigator. В последнем случае приложение называется аплетом.
   Первое приложение, которое мы рассмотрим, относится к простым приложениям, 
второе будет аплетом, встроенным в документ HTML.
   Подготовка и запуск приложения
   Итак, запустите среду Microsoft Developer Studio, сделав двойной щелчок 
левой клавишей мыши по соответствующей пиктограмме.
   Выберите из меню File строку New. На экране появится диалоговая панель New, 
показанная на рис. 2.1.

Рис. 2.1. Диалоговая панель New
   В этой диалоговой панели вам нужно выбрать строку Project Workspace и нажать 
кнопку OK. Сразу влсед за этим вы увидите диалоговую панлеь New Project 
Workspace (рис. 2.2).

Рис. 2.2. Диалоговая панель New Project Workspace
   В левой части этой диалоговой панели есть список различных типов проектов, 
которые можно создать. К теме нашей книги относятся проекты типа Java Workspace 
и Java Applet Wizard. Первый из них предназначен для ручного создания 
приложений и аплетов Java, второй позволяет создавать заготовки аплетов Java в 
полуавтоматическом режиме.
   Выберите проект типа Java Workspace. В поле Location укажите каталог, в 
котором будут создаваться проекты, а в поле Name - имя проекта. Переключатель 
Java Virtual Machine должен находиться во включенном состоянии.
   После заполнения диалоговой панели New Project Workspace нажмите кнопку 
Create. Будет создан проект, в котором пока нет ни одного исходного файла.
   Далее из меню Insert среды разработки Microsoft Developer Studio выберите 
строку Files into Project. В поле Filename наберите имя файла hello.java и 
нажмите кнопку Add. На экране появится сообщение о том, что файл с указанным 
именем не существует. Вы, однако, можете добавить ссылку на этот файл в проект, 
нажав кнопку Yes.
   На следующем этапе вы должны открыть папку файлов проекта, отмеченную как 
hello files на средней странице блокнота, расположенного в левой части окна 
системы разработки (рис. 2.3).

Рис. 2.3. В проект добавлен файл hello.java
   Сделайте двойной щелчок по имени файла hello.java. На экране появится 
сообщение о том, что файла с указанным именем не существует. Для того чтобы 
создать его, нажмите кнопку Yes.
   В правой части главного окна системы разработки появится окно редактирования,
 в котором вам нужно ввести исходный текст нашей программы, приведенный в 
листинге 2.1.
Листинг 2.1. Файл hello\hello.java
public class hello
{
  public static void main(String[] args)
  {
    System.out.println("Hello, Java!\n");
  }
}
   Затем выберите из меню Build строку Execute. На экране появится сообщение о 
том, что запускаемый файл hello.class не существует (рис. 2.4).

Рис. 2.4. Сообщение о том, что файл hello.class не существует
   Для создания файла нажмите кнопку Yes. Исходный текст программы будет 
откомпиллирован. Если вы ввели его правильно, сообщения об ошибках не появятся.
   Затем вы увидите на экране диалоговую панель Information For Running Class, 
показанную на рис. 2.5.

Рис. 2.5. Диалоговая панель Information For Running Class
   В поле Class file name вам нужно ввести строку hello. Это имя класса и оно 
должно в точности соответствовать имени класса, указанному в определении класса 
(листинг 2.1), а также имени файла, в котором определен класс.
   Заметим, что для каждого класса типа public вы должны создавать отдельный 
файл. Имя этого файла должно быть таким же, что и имя класса (с учетом строчных 
и прописных букв), а расширение имени файла необходимо указать как class.
   Указав имя класса, включите переключатель Stand-alone interpreter в поле Run 
project under. При этом ваша программа будет выполняться под управлением 
автономного интерпретатора Java jview.exe, который находится в каталоге Windows.

   После того как вы нажмете кнопку OK, ваше приложение будет запущено. На 
короткое время вы увидите окно интерпретатора Java, в котором появится 
сообщение Hello, Java! (рис. 2.6).

Рис. 2.6. Окно интерпретатора Java с сообщением, выведенным нашим приложением
   Не огорчайтесь, что это окно быстро исчезло - вы можете запустить программу 
под отладкой и наслаждаться видом окна сколько угодно, остановив работу 
программы после оператора вывода сообщения. Давайте попробуем сделать это.
   Установите курсор на оператор System.out.println("Hello, Java!\n") и нажмите 
комбинацию клавиш . Работа программы будет остановлена в момент 
достижения указанной строки. На экране появится окно интерпретатора jview.exe, 
не содержащее пока никаких сообщений.
   Далее выполняйте программу по шагам, нажимая клавишу . После первого 
раза в окне интерпретатора появится сообщение Hello, Java!. На второй раз 
приложение завершит свою работу и окно интерпретатора Java исчезнет с экрана.
   Взгляд на исходный текст приложения Hello
   Давайте теперь взглянем еще раз на исходный текст приложения Hello и 
посмотрим, что там к чему. Так как этот текст невелик, для удобства приведем 
его снова: 
public class hello
{
  public static void main(String[] args)
  {
    System.out.println("Hello, Java!\n");
  }
}
   В приложении определен один класс public с именем hello. Исходный файл 
приложения Java может содержать только один класс public, причем имя файла 
должно в точности соответствовать имени такого класса. В данном случае исходный 
файл называется hello.java. Если бы вы назвали файл Hello.java, компилятор 
выдал бы сообщение об ошибке.
   В классе hello мы определили один статический метод с именем main. 
Статическая функция с этим именем является точкой входа приложения Java, если 
она определена с классе public с именем, таким же как и имя файла. 
   В качестве параметра функции main передается ссылка на массив строк класса 
String. Через эти строки вы можете передавать приложению Java параметры запуска.

   Как наше приложение выводит текстовую строку на консоль?
   В классе System определена переменная класса PrintStream с именем out. В 
классе PrintStream определен метод println, при помощи которой наше приложение 
выводит сообщение “Hello, Java!” на консоль.
   Но где же объект, для которого вызывается метод println? В классе System 
поле PrintStream определено как статическое, поэтому методы этого класса можно 
вызывать, не создавая объектов класса, чем мы и воспользовались.
   Как видите, текст пристейшего приложения Java по своей сложности не намного 
превосходит исходный текст программы аналогичного назначения, составленной на 
языке программирования С.
   Простейший аплет
   Аплетами называются приложения Java, которые выполняются под управлением 
виртуальной машины Java, встроенной в навигатор, такой как Microsoft Internet 
Explorer или Netscape Navigator. Аплет встраивается в документ HTML и выглядит 
как окно заранее заданного размера. Он может рисовать в своем окне (и только в 
нем) произвольные изображения или текст.
   Двоичный файл с исполняемым (а точнее говоря, интерпретируемым) кодом Java 
располагается на сервере WWW. В документе HTML с помощью оператора  
организуется ссылка на этот двоичный файл. 
   Когда пользователь загружает в навигатор документ HTML с аплетом, файл 
аплета переписывается с сервера WWW на рабочую станцию пользователя. После 
этого навигатор начинает его выполнение.
   Возможно, вам не понравится такая идея, как запуск чужого аплета на своем 
компьютере - мало ли чего этот аплет может там сделать. Однако аплеты, в 
отличие от обычных приложений Java, сильно ограничены в своих правах. Например, 
они не могут читать локальные файлы и тем более в них писать. Есть также 
ограничения и на передачу данных через сеть: аплет может передавать данные 
только тому серверу WWW, с которого он загружен. В крайнем случае вы можете 
совсем отказаться от использования аплетов, отключив возможность их загрузки 
соответствующей настройкой навигатора. Но мы пока не будем этого делать, так 
как аплеты являются предметом изучения в нашей книге.
   Давайте создадим простейший аплет, воспользовавшись для этого системой 
автоматизированной разработки шаблонов аплета Java Applet Wizard, встроенной в 
Microsoft Visual J++.
   Запустите систему Microsoft Visual J++ и выберите из меню File строку New. В 
появившейся на экране диалоговой панели New выберите строку New Project 
Workspace. Затем вам нужно выбрать тип проекта Java Applet Wizard, как это 
показано на рис. 2.7.

Рис. 2.7. Выбор типа проекта Java Applet Wizard
   В поле Name введите имя приложения HelloAp, а в каталоге Location укажите 
путь к каталогу, в котором будут созданы файлы проекта. Затем нажмите кнопку 
Create. Вслед за этим на экране появится по очереди несколько диалоговых 
панелей, в которых вы должны описать создаваемый аплет. 
   Первая такая диалоговая панель показана на рис. 2.8.

Рис. 2.8. Выбор типа приложения, названия класса и степени подробности 
создаваемых комментариев к исходному тексту
   В поле How would you like to be able to run your program включите 
переключатель As an applet only. При этом создаваемое приложение сможет 
работать только под управлением навигатора.
   Имя класса аплета нужно указать в поле What would you like to name your 
applet class. Оставьте имя HelloAp, которое там есть по умолчанию.
   Состояние переключателей в поле Would you like to generate source file 
comments влияет на то, насколько подробно будут комментироваться создаваемый 
исходный текст приложения, и будет ли он комментироваться вообще.
   Если включить переключатель Yes, please, в исходный текст будут добавлены 
комментарии. Если же включить переключатель No, thank you, никаких комментариев 
не будет.
   При включении переключателя Explanatory comments в исходный текст будут 
включены комментарии, объясняющие назначение отдельных фрагментов кода. 
Переключатель TODO влияет на то, будут ли отмечены места исходного текста, в 
который вы должны вставить свой код, наполняющий аплет реальной жизнью.
   Завершив заполнение первой диалоговой панели, нажмите кнопку Next и 
переходите к следующей панели, показанной на рис. 2.9.

Рис. 2.9. Запрос на создание документа HTML и определение размера окна аплета
   Система Java Applet Wizard может создать для вас образец документа HTML, в 
который будет включен разрабатываемый вами аплет. Для этого во второй 
диалоговой панели вы должны включить переключатель Yes, please, расположенный в 
поле Would you like a sample HTML file.
   Начальные размеры окна, создаваемого в документе HTML для аплета, 
определяются в полях Width in pixels и Height in pixels (соответственно, ширина 
и высота). Заметим, что аплет может изменять размеры своего окна, о чем мы еще 
будем говорить.
   Третья диалоговая панель показана на рис. 2.10.

Рис. 2.10. Вопросы, связанные с мультизадачностью, анимацией и обработкой 
сообщений от мыши
   В этой панели вы должны указать, будет ли ваш аплет создавать задачи. Наш 
первый аплет однозадачный, поэтому в поле Would you like your applet to be 
multi-threaded вы должны вклюить переключатель No, thank you.
   На вопрос Would you like support for animation вы сможете ответить 
утвердительно только в том случае, если ваш аплет мультизадачный.
   Три переключателя, расположенные в поле Which mouse event handlers would you 
like added, позволят вам автоматически добавить обработчики сообщений от мыши. 
Пока не включайте их, так как мышью мы займемся позже.
   Следующая, четвертая диалоговая панель показана на рис. 2.11.

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

Рис. 2.12. Редактирование информации, описывающей аплет
   Эта информация будет возвращаться методом getAppInfo, определенным в классе 
аплета. При необходимости измените строки описания и нажмите кнопку Next.
   Финальная диалоговая панель показана на рис. 2.13.

Рис. 2.13. Финальная диалоговая панель
   Здесь вы можете последний раз перед созданием файлов проекта посмотреть на 
заданные вами параметры. Если нажать кнопку OK, проект будет создан. Для отказа 
от создания проекта нажмите кнопку Cancel.
   В результате работы системы Java Applet Wizard будет создано два файла (не 
считая файла проекта). Это исходный текст аплета HelloAp.java (листинг 2.2) и 
исходный текст документа HTML HelloAp.html, в который включен создаваемый аплет 
(листинг 2.3).
Листинг 2.2. Файл HelloAp\HelloAp.java (комментарии переведены на русский язык)
//**********************************************************
// HelloAp.java:	Applet
//
//**********************************************************
import java.applet.*;
import java.awt.*;

//==========================================================
// Основной класс для аплета HelloAp
//
//==========================================================
public class HelloAp extends Applet
{
  // Конструктор класса HelloAp
  //---------------------------------------------------
  public HelloAp()
  {
    // Сделать: Добавьте сюда код конструктора
  }

  // Обеспечение информации об аплете:
  //
  //   Метод getAppletInfo возвращает строку, которая
  // описывает аплет. Вы можете приведсти такую информацию,
  // как имя автора и дата создания, а так же любые другие
  // сведения об аплете
  //------------------------------------------------------
  public String getAppletInfo()
  {
    return "Name: HelloAp\r\n" +
      "Author: Alexandr Frolov\r\n" +
      "Created with Microsoft Visual J++ Version 1.0";
  }

  // Метод init вызывается системой AWT при первой загрузке
  // или перезагрузке аплета. Вы можете переопределить этот
  // метод для выполнения еобходимой инициализации аплета,
  // например, инициализации структур данных, загрузку
  // изображений или шрифтов, создание окон фреймов,
  // установку системы управления внешним видом или
  // добавление элементов пользовательского интерфейса
  //------------------------------------------------------
  public void init()
  {
    // Если для размещения в окне аплета органов управления 
    // вы используете класс "control creator", созданный 
    // системой ResourceWizard, из метода init можно 
    // вызывать метод CreateControls. Удалите вызов функции
    // resize перед добавлением вызова функции 
    // CreateControls, так как эта функция выполняет
    // изменение размера окна аплета самостоятельно
    //--------------------------------------------------
    resize(320, 240);

    // Сделать: Добавьте сюда дополнительный код 
    // инициализации
  }

  // Разместите здесь дополнительный код, необходимый
  // для “чистого” завершения работы аплета. Метод
  // destroy вызывается, когда аплет завершает работу
  // и будет выгружен из памяти
  //---------------------------------------------------
  public void destroy()
  {
    // Сделать: Добавьте сюда код завершения работы аплета
  }

  // Обработчик процедуры рисования окна аплета HelloAp 
  //---------------------------------------------------
  public void paint(Graphics g)
  {
    g.drawString(
      "Created with Microsoft Visual J++ Version 1.0", 
      10, 20);
  }

  //    Метод start вызывается при первом появлении на 
  // экране страницы HTML с аплетом
  //---------------------------------------------------
  public void start()
  {
    // Сделать: Добавьте сюда дополнительный код,
    //   который должен работать при запуске аплета
  }
	
  //    Метод stop вызывается когда страница HTML с 
  // аплетом исчезает с экрана
  //---------------------------------------------------
  public void stop()
  {
    // Сделать: Добавьте сюда дополнительный код,
    //   который должен работать при остановке аплета
  }
  // Сделать: Добавьте сюда дополнительный код
}
Листинг 2.3. Файл HelloAp\HelloAp.html


HelloAp




The source. Исходные файлы аплета HelloAp Как мы уже говорили, в результате работы системы Java Applet Wizard созданы файлы HelloAp.java и HelloAp.html. Рассмотрим их по отдельности. Файл HelloAp.java Исходный текст аплета HelloAp начинается с двух строк, подключающих оператором import библиотеки классов: import java.applet.*; import java.awt.*; Оператор import должен располагаться в файле исходного текста перед другими операторами (за исключением операторов комментария). В качестве параметра оператору import передается имя подключаемого класса из библиотеки классов. Если же необходимо подключить все классы данной библиотеки (как в нашем случае), вместо имени класса указывается символ “*”. Мы уже перечисляли библиотеки классов Java. Напомним, что библиотека java. applet содержит классы, необходимые для создания аплетов, то есть разновидности приложений Java, встраиваемых в документы HTML и работающих под управлением навигатора Internet. С помощью классов библиотеки java.awt аплет может выполнять в своем окне рисование различных изображений или текста, причем данный метод рисования не зависит от платформы, на которой работает аплет. Далее в исходном тексте аплета определяется класс типа public с именем HelloAp, которое должно обязательно совпадать с именем файла, содержащего исходный текст этого класса: public class HelloAp extends Applet { . . . } Определенный нами класс HelloAp с помощью ключевого слова extends наследуется от класса Applet. При этом методам класса HelloAp становятся доступными все методы и данные класса, за исключением определенных как private. Класс Applet определен в библиотеке классов java.applet, которую мы подключили оператором import. Создавая файл HelloAp.java, система Java Applet Wizard определили в классе HelloAp конструктор и несколько методов, заменив некоторые методы базового класса Applet. Конструктор HelloAp Конструктор клсса HelloAp находится в одноименном методе и вызывается при создании объекта класса: public HelloAp() { // Сделать: Добавьте сюда код конструктора } По умолчанию тело конструктора, создаваемого системой Java Applet Wizard, не содержит никакого кода. Однако вы можете добавить сюда строки, выполняющие инициализацию аплета при его создании как объекта. Метод getAppletInfo Базовый класс Applet содержит определение метода getAppletInfo, возвращающее значение null. В нашем классе HelloAp, который является дочерним по отношению к классу Applet, система Java Applet Wizard переопределила метод getAppletInfo из базового класса следующим образом: public String getAppletInfo() { return "Name: HelloAp\r\n" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } Теперь этот метод возвращает текстовую информацию об аплете в виде объекта класса String. Заметьте, что здесь возвращается достаточно длинная строка. Она разделена на три части, но способ разделения отличен от принятого в языке программирования С: части строки объединены оператором “+”. Для объектов класса String этот оператор в языке Java переопределен и имеет очевидное назначение - слияние строк. Метод init Метод init, так же как и метод getAppletInfo, определен в базовом классе Applet, от которого наследуются все аплеты. Определение его таково, что этот метод ровным счетом ничего не делает. Когда вызывается метод init и зачем он нужен? Метод init вызывается тогда, когда навигатор Internet загружает в свое окно документ HTML с оператором , ссылающимся на данный аплет. В этот момент аплет может выполнять инициализацию, например, создавать задачи, если он работает в мультизадачном режиме. Существует контрпара для метода init - метод destroy. О нем мы расскажем немного позже. Система Java Applet Wizard переопределяет метод init следующим образом: public void init() { resize(320, 240); // Сделать: Добавьте сюда дополнительный код // инициализации } Здесь вызывается метод resize, который изменяет размер окна аплета. Этот метод определен в базовом классе Applet. В нашем классе вы можете вызывать его потому, что мы образовали этот класс от класса Applet. Забегая вперед, скажем, что параметры оператора , с помощью которого аплет встраивается в документ HTML, допускают установку размеров окна аплета. Пользуясь методом resize, вы можете изменить эти размеры. Если же вы желаете изменять размеры окна, редактируя параметры оператора в документе HTML, вы должны удалить вызов метода resize из исходного текста метода init. Метод destroy Перед удалением аплета из памяти вызывается метод destroy, который определен в базовом классе Applet как пустая заглушка. Система Java Applet Wizard добавляет в исходный текст класса переопределение метода destroy, которое выглядит следующим образом: public void destroy() { // Сделать: Добавьте сюда код завершения работы аплета } Здесь вы можете выполнить все необходимые операции, которые следует выполнить перед удалением аплета. Например, если в методе init вы создавали какие-либо задачи, в методе destroy вы можете их завершить. Метод start Метод start вызывается после метода init в момент, когда пользователь начинает просматривать документ HTML с встроенным в него аплетом. Система Java Applet Wizard создает заглушку, переопределяющую метод start из базового класса: public void start() { // Сделать: Добавьте сюда дополнительный код, // который должен работать при запуске аплета } Вы можете модифицировать текст этого метода, если при каждом посещении пользователем страницы с аплетом необходимо выполнять какую-либо инициализацию. Метод stop Дополнением к методу start служит метод stop. Он вызывается, когда пользователь покидает страницу с аплетом и загружает в окно навигатора другую страницу. Заметим, что метод stop вызывается перед методом destroy. По умолчанию система Java Applet Wizard переопределяет метод stop базового класса Applet следующим образом: public void stop() { // Сделать: Добавьте сюда дополнительный код, // который должен работать при остановке аплета } Метод paint Наиболее интересен для нас метод paint, который выполняет рисование в окне аплета. Вот его исходный текст, созданный системой Java Applet Wizard: public void paint(Graphics g) { g.drawString( "Created with Microsoft Visual J++ Version 1.0", 10, 20); } Если посмотреть определение класса Applet, то в нем нет метода paint. В каком же классе определен этот метод? Взглянем на определение класса Applet: public class java.applet.Applet extends java.awt.Panel { . . . } Во-первых, вы видите, что полное имя класса Applet есть java.applet.Applet. Включая оператором import библиотеку классов java.applet.*, мы включали и определение класса Applet. Во-вторых, из определения класса можно заключить, что класс java.applet. Applet произошел от класса java.awt.Panel. Напомним, что определение классов java.awt.* также было включено в исходный текст нашего аплета оператором import. Если класс java.applet.Applet был создан на базе класса java.awt.Panel, то нет ли в базовом классе определения метода paint? Изучив исходный текст класса java.awt.Panel, убеждаемся, что такого метода там нет, однако сам класс java.awt.Panel произошел от класса java.awt. Container: public class java.awt.Panel extends java.awt.Container { . . . } Продолжим наши исследования. В классе java.awt.Container снова нет метода paint, но сам этот класс создан на базе класса java.awt.Component: public abstract class java.awt.Container extends java.awt.Component { . . . } Но и здесь метода paint нет. Этот метод определен в классе java.awt. Component, который, в свою очередь, произошел от класса java.lang.Object и реализует интерфейс java.awt.image.ImageObserver: public abstract class java.awt.Component extends java.lang.Object implements java.awt.image.ImageObserver { . . . } Мы проследили иерархию классов от класса java.applet.Applet, на базе которого создан наш аплет, до класса java.lang.Object, который является базовым для всех классов в Java. Схематически эту иерархию можно изобразить так: java.lang.Object (корневой класс) -> java.awt.Component -> java.awt.Container -> java.awt.Panel -> java.applet.Applet Метод paint определен в классе java.awt.Component, но так как этот класс является базовым для класса Applet и для нашего класса HelloAp, мы можем переопределить метод paint. Теперь о том, когда вызывается метод paint. Этот метод вызывается, когда необходимо перерисовать окно аплета. Если вы создавали приложения для операционной системы Microsoft Windows, то наверняка знакомы с сообщением WM_PAINT, которое поступает в функцию окна приложения при необходимости его перерисовки. Перерисовка окна приложения Windows и окна аплета обычно выполняется асинхронно по отношению к работе приложения или аплета. В любой момент времени аплет должен быть готов перерисовать содержимое своего окна. Такая техника отличается о той, к которой вы, возможно, привыкли, создавая обычные программы для MS-DOS. Программы MS-DOS сами определяют, когда им нужно рисовать на экране, причем рисование может выполняться из разных мест программы. Аплеты, так же как и приложения Windows, выполняют рисование в своих окнах централизованно. Аплет делает это в методе paint, а приложение Windows - при обработке сообщения WM_PAINT. Обратите внимание, что методу paint в качестве параметра передается ссылка на объект Graphics: public void paint(Graphics g) { . . . } По своему смыслу этот объект напоминает контекст отображения, с которым хорошо знакомы создатели приложений Windows. Контекст отображения - это как бы холст, на котором аплет может рисовать изображение или писать текст. Многочисленные методы класса Graphics позволяют задавать различные параметры холста, такие, например, как цвет или шрифт. Наше приложение вызывает метод drawString, который рисует текстовую строку в окне аплета: g.drawString( "Created with Microsoft Visual J++ Version 1.0", 10, 20); Вот прототип этого метода: public abstract void drawString(String str, int x, int y); Через первый параметр методу drawString передается текстовая строка в виде объекта класса String. Второй и третий параметр определяют, соответственно, координаты точки, в которой начнется рисование строки. В какой координатной системе? Аплеты используют систему координат, которая соответствует режиму отображения MM_TEXT, знакомому тем, кто создавал приложения Windows. Начало этой системы координат расположено в левом верхнем углу окна аплета, ось X направлена слева направо, а ось Y - сверху вниз (рис. 2.14). Рис. 2.14. Система координат, используемая методом drawString На этом же рисунке показано, как метод drawString нарисует текстовую строку с координатами (xCoord, yCoord). Более подробно вопросы рисования в окне аплета мы рассмотрим в третьей главе нашей книги, которая так и называется - “Рисование в окне аплета”. Файл HelloApp.html Файл HelloApp.html автоматически создается системой Java Applet Wizard, если это было указано во второй диалоговой панели, задающей параметры нового проекта. Нас интересует в этом файле оператор , который используется в паре с оператором и предназначен для встраивания окна аплета в документ HTML. Вот как выглядит фрагмент документа HTML, созданного для нашего проекта, в котором встраивается аплет: Рассмотрим параметры оператора , указанные в этом фрагменте кода, а также некоторые другие. ПараметрОписаниеALIGNВыравнивание окна аплета относительно окружающего его текста. Возможны следующие значения: LEFT выравнивание влево относительно окружающего текста; CENTER центрирование; RIGHT выравнивание вправо относительно окружающего текста; TOP выравнивание по верхней границе; MIDDLE центрирование по вертикали; BOTTOM выравнивание по нижней границеALTС помощью этого параметра можно задать текст, который будет отображаться в окне аплета в том случае, если навигатор не может работать с аплетами JavaCODEИмя двоичного файла, содержащего код аплета. По умолчанию путь к этому файлу указывается относительно каталога с файлом HTML, в который встроен аплет. Такое поведение может быть изменено параметром CODEBASECODEBASEБазовый адрес URL аплета, то есть путь к каталогу, содержащему аплетHEIGHTНачальная ширина окна аплета в пикселахWIDTHНачальная высота окна аплета в пикселахHSPACEЗазор слева и справа от окна аплетаVSPACEЗазор сверху и снизу от окна аплетаNAMEИдентификатор аплета, который может быть использован другими аплетами, расположенными в одном и том же документе HTMLTITLEСтрока заголовка Дополнительно между операторами и вы можете задать параметры аплета. Для этого используется оператор , который мы рассмотрим позже. Внешний вид окна навигатора, в котором отображается созданный документ HTML с аплетом, показан на рис. 2.15. Рис. 2.15. Окно аплета в документе HTML Обратите внимание, что границы окна аплета никак не выделяются, а цвет его фона совпадает с цветом фона документа HTML. В следующей главе мы научим вас изменять цвет фона окна аплета и текста, отображаемого в этом окне. В нижней части окна под разделительной линией находится ссылка на исходный текст аплета: The source. Вы можете использовать эту ссылку для просмотра содержимого файла HelloAp. java. Упрощаем исходный текст аплета Если вам показалось, что исходный текст аплета слишком сложный, вы можете его упростить, как это показано ниже: //********************************************************** // HelloAp.java: Applet // //********************************************************** import java.applet.*; import java.awt.*; //========================================================== // Основной класс для аплета HelloAp // //========================================================== public class HelloAp extends Applet { // Обработчик процедуры рисования окна аплета HelloAp //--------------------------------------------------- public void paint(Graphics g) { g.drawString( "Created with Microsoft Visual J++ Version 1.0", 10, 20); } } Мы выбросили определения всех методов, которые не выполняют никакой полезной работы, а также удалили методы getAppletInfo и init. Аплет будет работать также, как и раньше, потому что методы init, start, stop, destroy, getAppletInfo, удаленные нами, определены в базовом классе Applet. Однако метод paint необходимо переопределить в любом случае, так как именно в нем выполняется рисование строки, то есть то, что делает наш аплет. Почему же система Java Applet Wizard создает пустые определения методов? Просто для того, чтобы вы при необходимости наполнили их чем нибудь полезным. Если вам не нужны эти определения, вы можете их удалить. 3 РИСОВАНИЕ В ОКНЕ АПЛЕТА В предыдущей главе мы привели простейший пример аплета, который выполняет рисование текстовой строки в своем окне. Теперь мы более подробно расскажем вам о том, что и как может рисовать аплет. Контекст отображения Способ, которым аплет выполняет рисование в своем окне, полностью отличается от того, которым пользуются программы MS-DOS. Вместо того чтобы обращаться напрямую или через драйвер к регистрам видеоконтроллера, аплет пользуется методами из класса Graphics. Эти методы инкапсулируют все особенности аппаратуры, предоставляя в распоряжение программиста платформно-независимое средство рисования. Для окна аплета создается объект класса Graphics, ссылка на который передается методу paint. В предыдущей главе мы уже пользовались этим объектом, вызывая для него метод drawString, рисующий в окне текстовую строку. Объект, ссылка на который передается методу paint, и есть контекст отображения. Сейчас мы займемся контекстом отображения вплотную. Полотно для рисования Проще всего представить себе контекст отображения как полотно, на котором рисует художник. Точно так же как художник может выбирать для рисования различные инструменты, программист, создающий аплет Java, может выбирать различные методы класса Graphics и задавать различные атрибуты контекста отображения. Методы класса Graphics В качестве базового для класса Graphics (полное название класса java.awt. Graphics) выступает классс java.lang.Object. В виду важности класса Graphics мы приведем его определение с комментариями: public abstract class java.awt.Graphics extends java.lang.Object { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- protected Graphics(); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Стирание содержимого прямоугольной области public abstract void clearRect(int x, int y, int width, int height); // Задание области ограничения вывода public abstract void clipRect(int x, int y, int width, int height); // Копирование содержимого прямоугольной области public abstract void copyArea(int x, int y, int width, int height, int dx, int dy); // Создание контекста отображения public abstract Graphics create(); // Создание контекста отображения public Graphics create(int x, int y, int width, int height); // Удаление контекста отображения public abstract void dispose(); // Рисование прямоугольной области с трехмерным // выделением public void draw3DRect(int x, int y, int width, int height, boolean raised); // Рисование сегмента public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle); // Рисование текста из массива байт public void drawBytes(byte data[], int offset, int length, int x, int y); // Рисование текста из массива символов public void drawChars(char data[], int offset, int length, int x, int y); // Рисование растрового изображения public abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer); // Рисование растрового изображения public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer); // Рисование растрового изображения public abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer); // Рисование растрового изображения public abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer); // Рисование линии public abstract void drawLine(int x1, int y1, int x2, int y2); // Рисование овала public abstract void drawOval(int x, int y, int width, int height); // Рисование многоугольника public abstract void drawPolygon(int xPoints[], int yPoints[], int nPoints); // Рисование многоугольника public void drawPolygon(Polygon p); // Рисование прямоугольника public void drawRect(int x, int y, int width, int height); // Рисование прямоугольника с круглыми углами public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight); // Рисование текстовой строки public abstract void drawString(String str, int x, int y); // Рисование заполненного прямоугольника с // трехмерным выделением public void fill3DRect(int x, int y, int width, int height, boolean raised); // Рисование заполненного сегмента круга public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle); // Рисование заполненного овала public abstract void fillOval(int x, int y, int width, int height); // Рисование заполненного многоугольника public abstract void fillPolygon(int xPoints[], int yPoints[], int nPoints); // Рисование заполненного многоугольника public void fillPolygon(Polygon p); // Рисование заполненного прямоугольника public abstract void fillRect(int x, int y, int width, int height); // Рисование заполненного прямоугольника // с круглыми углами public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight); // Прослеживание вызова метода dispose public void finalize(); // Определение границ области ограничения вывода public abstract Rectangle getClipRect(); // Определение цвета, выбранного в контекст отображения public abstract Color getColor(); // Определение шрифта, выбранного в контекст отображения public abstract Font getFont(); // Определение метрик текущего шрифта public FontMetrics getFontMetrics(); // Определение метрик заданного шрифта public abstract FontMetrics getFontMetrics(Font f); // Установка цвета для рисования в контексте отображения public abstract void setColor(Color c); // Установка текущего шрифта в контексте отображения public abstract void setFont(Font font); // Установка режима рисования public abstract void setPaintMode(); // Установка маски для рисования public abstract void setXORMode(Color c1); // Получение текстовой строки, представляющей // данный контекст отображения public String toString(); // Сдвиг начала системы координат // в контексте отображения public abstract void translate(int x, int y); } Рассмотрим назначение основных методов, сгруппировав их по выполняемым функциям. Установка атрибутов контекста отображения Изменяя атрибуты контекста отображения, приложение Java может установить цвет для рисования графических изображений, таких как линии и многоугольники, шрифт для рисования текста, режим рисования и маску. Возможен также сдвиг начала системы координат. Выбор цвета Изменение цвета, выбранного в контекст отображения, выполняется достаточно часто. В классе Graphics для изменения цвета определен метод setColor, прототип которого представлен ниже: public abstract void setColor(Color c); В качестве параметра методу setColor передается ссылка на объект класса Color, с помощью которого можно выбрать тот или иной цвет. Как задается цвет? Для этого можно использовать несколько способов. Прежде всего, вам доступны статические объекты, определяющие фиксированный набор основных цветов: ОбъектЦветpublic final static Color black;черныйpublic final static Color blue; голубойpublic final static Color cyan;цианpublic final static Color darkGray; темно-серыйpublic final static Color gray;серыйpublic final static Color green; зеленыйpublic final static Color lightGray;светло-серыйpublic final static Color magenta;малиновыйpublic final static Color orange;оранжевыйpublic final static Color pink;розовыйpublic final static Color red;красныйpublic final static Color white;белыйpublic final static Color yellow;желтый Этим набором цветов пользоваться очень просто: public void paint(Graphics g) { // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); g.drawString("Привет из аплета!", 10, 20); . . . } Здесь мы привели фрагмент исходного текста метода paint, в котором в контексте отображения устанавливается желтый цвет. После этого метод drawString выведет текстовую строку "Привет из аплета!" желтым цветом. Если необходима более точная установка цвета, вы можете воспользоваться одним из трех конструкторов объекта Color: public Color(float r, float g, float b); public Color(int r, int g, int b); public Color(int rgb); Первые два конструктора позволяют задавать цвет в виде совокупности значений трех основных цветовых компонент - красной, желтой и голубой (соотвестсвенно, параметры r, g и b). Для первого конструктора диапазон возможных значений компонент цвета находится в диапазоне от 0.0 до 1.0, а для второго - в диапазоне от 0 до 255. Третий конструктор также позволяет задавать отдельные компоненты цвета, однако они должны быть скомбинированы в одной переменной типа int. Голубая компонента занимает биты от 0 до 7, зеленая - от 8 до 15, красная - от 16 до 23. Ниже мы привели пример выбора цвета с помощью конструктора, передав ему три целочисленных значения цветовых компонент: g.setColor(new Color(0, 128, 128)); В классе Color определено еще несколько методов, которые могут оказаться вам полезными: МетодОписаниеpublic Color brighter();Установка более светлого варианта того же цветаpublic Color darker();Установка более темного варианта того же цветаpublic boolean equals( Object obj);Проверка равенства цветов текущего объекта и объекта, заданного параметромpublic int getBlue();Определение голубой компоненты цвета (в диапазоне от 0 до 255)public int getRed();Определение красной компоненты цвета (в диапазоне от 0 до 255)public int getGreen();Определение зеленой компоненты цвета (в диапазоне от 0 до 255)getHSBColor(float h, float s, float b);Определение компонент оттенка, насыщенности и яркости (схема HSB) public int getRGB();Определение компонент RGB для цвета, выбранного в контекст отображенияpublic static int HSBtoRGB(float hue, float saturation, float brightness);Преобразование цветового представления из схемы HSB в схему RGBpublic static float[] RGBtoHSB(int r, int g, int b, float hsbvals[]);Преобразование, обратное выполняемому предыдущей функциейpublic String toString();Получение текстовой строки названия цвета Второй способ установки цвето фона и изображения заключается в вызове методов setBackground и setForeground, например: setBackground(Color.yellow); setForeground(Color.black); Здесь мы устанавливаем для окна аплета желтый цвет фона и черный цвет изображения. Выбор шрифта С помощью метода setFont из класса Graphics вы можете выбрать в контекст отображения шрифт, который будет использоваться методами drawString, drawBytes и drawChars для рисования текста. Вот прототип метода setFont: public abstract void setFont(Font font); В качестве параметра методу setFont следует передать объект класса Font: public class java.awt.Font extends java.lang.Object { // ----------------------------------------------------- // Поля класса // ----------------------------------------------------- protected String name; protected int size; protected int style; // Битовые маски стиля шрифта public final static int BOLD; public final static int ITALIC; public final static int PLAIN // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public Font(String name, int style, int size); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Сравнение шрифтов public boolean equals(Object obj); // Определение названия семейства шрифтов public String getFamily(); // Получение шрифта по его характеристикам public static Font getFont(String nm); public static Font getFont(String nm, Font font); // Определение названия шрифта public String getName(); // Определение размера шрифта public int getSize(); // Определение стиля шрифта public int getStyle(); // Получение хэш-кода шрифта public int hashCode(); // Определение жирности шрифта public boolean isBold(); // Проверка, является ли шрифт наклонным public boolean isItalic(); // Проверка, есть ли шрифтовое выделение public boolean isPlain(); // Плучение текстовой строки для объекта public String toString(); } Создавая шрифт конструктором Font, вы должны указать имя, стиль и размер шрифта. В качестве имени можно указать, например, строки Arial или Courier. Учтите, что в системе удаленного пользователя, загрузившего ваш аплет, может не найтись шрифта с указанным вами именем. В этом случае навигатор заменит его на наиболее подходящий (с его точки зрения). Стиль шрифта задается масками BOLD, ITALIC и PLAIN, которые можно комбинировать при помощи логической операции “ИЛИ”: МаскаОписаниеBOLDУтолщенный шрифтITALICНаклонный шрифтPLAINШрифтовое выделение не используется Что же касается размера шрифта, то он указывается в пикселах. Установка режима рисования Метод setPaintMode устанавливает в контексте отображения режим рисования, при котором выполняется замещение изображения текущим цветом, установленном в контексте отображения. Прототип метода setPaintMode приведен ниже: public abstract void setPaintMode(); Установка маски для рисования Задавая маску для рисования при помощи метода setXORMode, вы можете выполнить при рисовании замещение текущего цвета на цвет, указанный в параметре метода, и наоборот, цвета, указанного в параметре метода, на текущий. Все остальные цвета изменяются непредсказуемым образом, однако эта операция обратима, если вы нарисуете ту же самую фигуру два раза на одном и том же месте. Прототип метода setXORMode: public abstract void setXORMode(Color c1); Сдвиг начала системы координат Метод translate сдвигает начало системы координат в контексте отображения таким образом, что оно перемещается в точку с координатами (x, y), заданными через параметры метода: public abstract void translate(int x, int y); Определение атрибутов контекста отображения Ряд методов класса Graphics позволяет определить различные атрибуты контекста отображения, например, цвет, выбранный в контекст отображения или метрики текущего шрифта, которым выполняется рисование текста. Рассмотрим методы, позволяющие определить атрибуты контекста отображения. Определение границ области ограничения вывода С помощью метода clipRect, о котором мы расскажем чуть позже, вы можете определить в окне аплета область ограничения вывода прямоугольной формы. Вне этой области рисование графических изображений и текста не выполняется. Метод getClipRect позволяет вам определить координаты текущей области ограничения, заданной в контексте отображения: public abstract Rectangle getClipRect(); Метод возвращает ссылку на объект класса Rectangle, который, в частности, имеет поля класса с именами x, y, height и width. В этих полях находится, соответственно, координаты верхнего левого угла, высота и ширина прямоугольной области. Определение цвета, выбранного в контекст отображения Метод getColor возвращает ссылку на объект класса Color, представляющий текущий цвет, выбранный в контекст отображения: public abstract Color getColor(); Определение шрифта, выбранного в контекст отображения С помощью метода getFont, возвращающего ссылку на объект класса Font, вы можете определить текущий шрифт, выбранный в контекст отображения: public abstract Font getFont(); Определение метрик текущего шрифта Несмотря на то что вы можете заказать шрифт с заданным именем и размером, не следует надеяться, что навигатор выделит вам именно такой шрифт, какой вы попросите. Для правильного размещения текста и других изображений в окне аплета вам необходимо знать метрики реального шрифта, выбранного навигатором в контекст отображения. Метрики текущего шрифта в контексте отображения вы можете узнать при помощи метода getFontMetrics, прототип которого приведен ниже: public FontMetrics getFontMetrics(); Метод getFontMetrics возвращает ссылку на объект класса FontMetrics. Ниже мы привели список наиболее важных методов этого класса, предназначенных для получения отдельных параметров шрифта: МетодОписаниеpublic Font getFont();Определение шрифта, который описывается данной метрикойpublic int bytesWidth( byte data[], int off, int len);Метод возвращает ширину строки символов, расположенных в массиве байт data. Параметры off и len задают, соответственно, смещение начала строки в массиве и ее длинуpublic int charsWidth( char data[], int off, int len);Метод возвращает ширину строки символов, расположенных в массиве символов data. Параметры off и len задают, соответственно, смещение начала строки в массиве и ее длинуpublic int charWidth( char ch);Метод возвращает ширину заданного символаpublic int charWidth( int ch);Метод возвращает ширину заданной строки символовpublic int getAscent();Определение расстояния от базовой линии до верхней выступающей части символовpublic int getDescent();Определение расстояния от базовой линии до нижней выступающей части символовpublic int getLeading();Расстояние между строками текстаpublic int getHeight();Определение полной высоты символов, выполняется по формуле: getLeading() + getAscent() + getDescent()public int getMaxAdvance();Максимальная ширина символов в шрифтеpublic int getMaxAscent();Максимальное расстояние от базовой линии до верхней выступающей части символов для символов данного шрифтаpublic int getMaxDescent();Максимальное расстояние от базовой линии до нижней выступающей части символов для символов данного шрифтаpublic int[] getWidths(); Массив ширин первых 256 символов в шрифтеpublic int stringWidth(String str);Ширина строки, передаваемой методу в качестве параметраpublic String toString();Тектовая строка, которая представляет данную метрику шрифта Обратите внимание на метод stringWidth, позволяющий определить ширину текстовой строки. Заметим, что без этого метода определение ширины текстовой строки было бы непростой задачей, особенно если шрифт имеет переменную ширину символов. Для определения полной высоты строки символов вы можете воспользоваться методом getHeight. Определение метрик заданного шрифта Метод getFontMetrics с параметром типа Font позволяет определить метрики любого шрифта, передаваемого ему в качетсве параметра: public abstract FontMetrics getFontMetrics(Font f); В отличие от нее метод getFontMetrics без параметров возвращает метрики текущего шрифта, выбранного в контекст отображения. Рисование геометрических фигур В этом разделе мы опишем методы класса Graphics, предназначенные для рисования элементарных геометрических фигур, таких как линии, прямоугольники, окружности и так далее. Линии Для того чтобы нарисовать прямую тонкую сплошную линию, вы можете воспользоваться методом drawLine, прототип которого приведен ниже: public abstract void drawLine(int x1, int y1, int x2, int y2); Концы линии имеют координаты (x1, y1) и (x2, y2), как это показано на рис. 3. 1. Рис. 3.1. Рисование прямой линии К сожалению, в контексте отображения не предусмотерны никакие атрибуты, позволяющие назрисовать пунктирную линию или линию увеличенной толщины. Прямоугольники и квадраты Среди методов класса Graphics есть несколько, предназначенных для рисования прямоугольников. Первый из них, с именем drawRect, позволяет нарисовать прямоугольник, заданный координатами своего левого вернего угла, шириной и высотой: public void drawRect(int x, int y, int width, int height); Параметры x и y задают, соответственно, координаты верхнего левого угла, а параметры width и height - высоту и ширину прямоугольника (рис. 3.2). Рис. 3.2. Рисование прямоугольника В отличие от метода drawRect, рисующего только прямоугольную рамку, метод fillRect рисует заполненный прямоугольник. Для рисования и заполнения прямоугольника используется цвет, выбранный в контекст отображения (рис. 3.3). Прототип метода fillRect приведен ниже: public abstract void fillRect(int x, int y, int width, int height); Рис. 3.3. Рисование заполненного прямоугольника Метод drawRoundRect позволяет нарисовать прямоугольник с закругленными углами: public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight); Параметры x и y определяют координаты верхнего левого угла прямоугольника, параметры width и height задают, соответственно его ширину и высоту. Размеры эллипса, образующего закругления по углам, вы можете задать с помощью параметров arcWidth и arcHeight. Первый из них задает ширину эллипса, а второй - высоту (рис. 3.4). Рис. 3.4. Рисование прямоугольника с закругленными углами Метод fillRoundRect позволяет нарисовать заполненный прямоугольник с закругленными углами (рис. 3.5). Назначение параметров этого метода аналогично назначению параметров только что рассмотренного метода drawRoundRect: public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight); Рис. 3.5. Рисование заполненного прямоугольника с закругленными углами Метод fill3Drect предназначен для рисования выступающего или западающего прямоугольника: public void fill3DRect(int x, int y, int width, int height, boolean raised); Если значение параметра raised равно true, рисуется выступающий прямоугольник, если false - западающий. Назначение остальных параметров аналогично назначению параметров метода drawRect. Многоугольники Для рисования многоугольников в классе Graphics предусмотрено четыре метода, два из которых рисуют незаполненные многоугольники, а два - заполненные. Первый метод рисует незаполненный многоугольник, заданный массивами координат по осям X и Y: public abstract void drawPolygon(int xPoints[], int yPoints[], int nPoints); Через параметры xPoints и yPoints передаются, соответственно, ссылки на массивы координат по оис X и Y. Параметр nPoints задает количество точек в массивах. На рис. 3.6 показан многоугольник, нарисованный методом drawPolygon. Рис. 3.6. Многоугольник, нарисованный методом drawPolygon В этом многоугольнике шесть вершин с координатами от (x0, y0) до (x5, y5), причем для того чтобы он стал замкнутым,координаты первой и последней вершины совпадают. Второй метод также рисует незаполненный многоугольник, однако в качетсве параметра методу передается ссылка на объект Polygon: public void drawPolygon(Polygon p); Класс Polygon достаточно прост, поэтому мы приведем его описание полностью: public class java.awt.Polygon extends java.lang.Object { // ----------------------------------------------------- // Поля класса // ----------------------------------------------------- public int npoints; // количество вершин public int xpoints[]; // массив координат по оси X public int ypoints[]; // массив координат по оси Y // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- public Polygon(); public Polygon(int xpoints[], int ypoints[], int npoints); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление вершины public void addPoint(int x, int y); // Получение координат охватывающего прямоугольника public Rectangle getBoundingBox(); // Проверка, находится ли точка внутри многоугольника public boolean inside(int x, int y); } Ниже мы показали фрагмент кода, в котором создается многоугольник, а затем в него добавляется несколько точек. Многоугольник рисуется методом drawPolygon: Polygon p = new Polygon(); p.addPoint(270, 239); p.addPoint(350, 230); p.addPoint(360, 180); p.addPoint(390, 160); p.addPoint(340, 130); p.addPoint(270, 239); g.drawPolygon(p); Если вам нужно нарисовать заполненный многоугольник (рис. 3.7), то для этого вы можете воспользоваться методами, приведенными ниже: public abstract void fillPolygon(int xPoints[], int yPoints[], int nPoints); public void fillPolygon(Polygon p); Первый из этих методов рисует многоугольник, координаты вершин которого заданы в массивах, второй - получая объект класса Polygon в качестве параметра. Рис. 3.6. Многоугольник, нарисованный методом drawPolygon Овалы и круги Для рисования окружностей и овалов вы можете воспользоваться методом drawOval: public abstract void drawOval(int x, int y, int width, int height); Параметры этого методы задают координаты и размеры прямоугольника, в который вписывается рисуемый овал (рис. 3.7). Рис. 3.7. Рисование овала Метод fillOval предназначен для рисования заполненного овала (рис. 3.8). Назначение его параметров аналогично назначению параметров метода drawOval: public abstract void fillOval(int x, int y, int width, int height); Рис. 3.7. Рисование заполненного овала Сегменты Метод drawArc предназначен для рисования незаполненного сегмента (рис. 3.8). Прототип этого метода приведен ниже: public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle); Рис. 3.8. Рисование незаполненного сегмента Параметры x, y, width и height задают координаты прямоугольника, в который вписан сегмент. Параметры startAngle и arcAngle задаются в градусах. Они определяют, соответственно, начальный угол и угол разворота сегмента. Для того чтобы нарисовать заполненный сегмент, вы можете воспользоваться методом fillArc: public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle); Задание области ограничения Если для окна аплета задать область ограничения, то рисование будет возможно только в пределах этой области. Область ограничения задается методом clipRect, прототип которого мы привели ниже: public abstract void clipRect(int x, int y, int width, int height); Параметры x, y, width и height задают координаты прямоугольной области ограничения. Копирование содержимого прямоугольной области Метод copyArea позволяет скопировать содержимое любой прямоугольной области окна аплета: public abstract void copyArea(int x, int y, int width, int height, int dx, int dy); Параметры x, y, width и height задают координаты копируемой прямоугольной области. Область копируется в другую прямоугольную область такого же размера, причем параметры dx и dy определяют координаты последней. Приложение Painter В этом разделе мы рассмотрим исходные тексты аплета Painter, в которых демонстрируется использование большинства только что описанных нами функций рисования. Внешний вид окна аплета при просмотре соответствующего документа HTML навигатором Microsoft Internet Explorer показано на рис. 3.9. Рис. 3.9. Окно аплета Painter Здесь мы написали текстовую строку, нарисовали несколько горизонтальных линий, скопировав это линии в другое место окна, а также изобразили несколько простейших геометрических фигур. Исходные файлы приложения Painter В листинге 3.1 мы привели исходный текст аплета Painter. Листинг 3.1. Файл Painter\Painter.java // ========================================================= // Аплет, демонстрирующий использование различных // функций рисования // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Painter extends Applet { // ------------------------------------------------------- // Painter // Конструктор не используется // ------------------------------------------------------- public Painter() { } // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Painter\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Для того чтобы размеры окна аплета можно было // задавать в документе HTML, закрываем следующую // строку символом комментария // resize(320, 240); } // ------------------------------------------------------- // destroy // Метод destroy не используется // ------------------------------------------------------- public void destroy() { } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Эта строка будет удалена из окна аплета // методом clearRect g.drawString("Невидимая строка", 10, 20); // Стираем содержимое окна аплета. Цвет окна // становится таким же, как и цвет фона // окна навигатора g.clearRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Пишем строку в верхнем левом углу окна аплета g.drawString("Привет из аплета!", 10, 20); // Рисуем в цикле пять горизонтальных линий for(int i = 0; i < 5; i++) { g.drawLine(10, 30 + (i * 10), 200, 30 + (i * 10)); } // Копируем область окна, занятую // нарисованными линиями g.copyArea(10, 30, 200, 50, 220, 0); // Выбираем в контекст отображения белый цвет g.setColor(Color.white); // Закрашиваем белвым цветом нижнюю часть окна g.fillRect(1, 80, dimAppWndDimension.width - 2, dimAppWndDimension.height - 81); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем два трехмерных прямоугольника g.draw3DRect(10, 100, 100, 20, true); g.draw3DRect(120, 100, 100, 20, false); // Выбираем в контекст отображения красный цвет g.setColor(Color.red); // Рисуем рамку, в которую будет вписан сегмент g.drawRect(10, 140, 200, 100); g.drawLine(10, 190, 210, 190); g.drawLine(110, 140, 110, 240); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем сегмент g.drawArc(10, 140, 200, 100, -25, 320); // Создаем многоугольник Polygon p = new Polygon(); // Добавляем в многоугольник несколько вершин p.addPoint(270, 239); p.addPoint(350, 230); p.addPoint(360, 180); p.addPoint(390, 160); p.addPoint(340, 130); p.addPoint(270, 239); // Рисуем многоугольник g.drawPolygon(p); // Рисуем прямоугольник с закругленными углами g.drawRoundRect(10, 250, 200, 100, 60, 40); // Выбираем в контекст отображения красный цвет g.setColor(Color.red); // Задаем область ограничения вывода g.clipRect(260, 250, 200, 100); // Рисуем круг, пересекающий область ограничения g.fillOval(300, 200, 170, 170); } // ------------------------------------------------------- // start // Метод start не используется // ------------------------------------------------------- public void start() { } // ------------------------------------------------------- // stop // Метод stop не используется // ------------------------------------------------------- public void stop() { } } Листинг 3.2 содержит исходный текст документа HTML, в который встроен данный аплет. Листинг 3.2. Файл Painter\Painter.html Painter

The source. Мы внесли небольшие изменения в файл Painter.html, подготовленный системой Java Applet Wizard. Эти изменения заключаются в увеличении размеров окна аплета, задаваемых параметрами WIDTH и HEIGHT оператора . Метод init Аплет Painter был создан с помощью системы Java Applet Wizard. Мы выполнили изменения методов init, getAppletInfo и paint. Изменения метода init заключаются в том, что мы закрыли символом комментария строку установки размеров окна аплета: public void init() { // resize(320, 240); } Это позволяет задавать размеры окна аплета не в исходном тексте приложения, а в параметрах оператора , с помощью которого аплет встраивается в документ HTML. Метод getAppletInfo В методе getAppletInfo изменения невелики: в описание аплета был добавлен наш почтовый адрес E-mail и адрес нашего сервера WWW: public String getAppletInfo() { return "Name: Painter\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } Метод paint В обработчик метода paint мы добавили функции рисования. В самом начале своей работы метод определяет текущие размеры окна аплета, вызывая для этого метод size: Dimension dimAppWndDimension = size(); Метод size определен в классе Component, от которого в конечном счете наследуется класс Applet и класс нашего приложения Painter. Этот метод возвращает ссылку на объект класса Dimension, хранящего высоту и ширину объекта: public class java.awt.Dimension extends java.lang.Object { // ----------------------------------------------------- // Поля класса // ----------------------------------------------------- public int height; // высота public int width; // ширина // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- public Dimension(); public Dimension(Dimension d); public Dimension(int width, int height); // ----------------------------------------------------- // Метод // ----------------------------------------------------- public String toString(); } На следующем шаге после определения размеров окна наше приложение рисует в окне строку, а затем стирает содержимое всего окна: g.drawString("Невидимая строка", 10, 20); g.clearRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); В качестве начальных координат стираемой области мы указали точку (0, 0) - это верхний левый угол окна аплета. Ширина и высота стираемой области задана исходя из размеров аплета, полученных от метода size. Для того чтобы изменить цвет фона окна, ме его закрашиваем (хотя могли бы воспользоваться и методом setBackground). Это можно сделать методом fillRect. Вначале при помощи метода setColor мы выбираем в контекст отображения желтый цвет, а затем закрашиваем всю внутреннюю область окна аплета методом fillRect: g.setColor(Color.yellow); g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); На следующем этапе метод paint выбирает в контекст отображения черный цвет и рисует черную рамку вокруг окна аплета, вызывая для этого метод drawRect: g.setColor(Color.black); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Далее при помощи метода drawString обработчик метода paint пишет в верхней части окна аплета строку, которая останется на экране: g.drawString("Привет из аплета!", 10, 20); Для того чтобы продемонстрировать работу функции copyArea, копирующей прямоугольную область окна аплета, мы нарисовали в окне пять горизонтальных прямых линий, а затем скопировали результат в правый верхний угол окна аплета: for(int i = 0; i < 5; i++) { g.drawLine(10, 30 + (i * 10), 200, 30 + (i * 10)); } g.copyArea(10, 30, 200, 50, 220, 0); Для рисования линий мы здесь вызываем метод drawLine. Далее вызывая метод fillRect после предварительного выбора в контекст отображения белого цвета мы закрашиваем белым цветом всю нижнюю часть окна аплета, подготавливая фон для дальнейших упражнений в рисовании: g.setColor(Color.white); g.fillRect(1, 80, dimAppWndDimension.width - 2, dimAppWndDimension.height - 81); Ширина и высота закрашиваемой области здесь указана с учетом наличия черной рамки толщиной в 1 пиксел вокруг окна аплета. В верхней части полученной таким образом области белого цвета приложение рисует два прямоугольника черного цвета с трехмерным выделением: g.setColor(Color.black); g.draw3DRect(10, 100, 100, 20, true); g.draw3DRect(120, 100, 100, 20, false); Затем мы приступаем к рисованию сегмента. Вначале в контекст отображения выбирается красный цвет. Этим цветом мы рисуем прямоугольную рамку, в которую будет вписан сегмент: g.setColor(Color.red); g.drawRect(10, 140, 200, 100); g.drawLine(10, 190, 210, 190); g.drawLine(110, 140, 110, 240); Рамка разделена по горизонтали и по вертикали красными линиями. Далее мы выбираем в контекст отображения черный цвет и рисуем сегмент: g.setColor(Color.black); g.drawArc(10, 140, 200, 100, -25, 320); Обратите внимание, что начальный угол сегмента имеет отрицательное значение. Угол сегмента составляет 320 градусов. Следующий шаг - создание и рисование многоугольника. Многоугольник создается как объект класса Polygon. К этому объекту с помощью метода addPoint мы добавляем несколько точек: Polygon p = new Polygon(); p.addPoint(270, 239); p.addPoint(350, 230); p.addPoint(360, 180); p.addPoint(390, 160); p.addPoint(340, 130); p.addPoint(270, 239); Для того чтобы многоугольник был образован замкнутой ломаной линией, первая и последняя точки имеют одинаковые координаты. После подготовки многоугольника он рисуется при помощи метода drawPolygon: g.drawPolygon(p); Наш аплет рисует также прямоугольник с закругленными углами, вызыая метод drawRoundRect: g.drawRoundRect(10, 250, 200, 100, 60, 40); Ширина и высота эллипсов закругления составляет, соответственно, 60 и 40 пикселов. Для демонстрации действия области ограничения вывода мы создаем такую область, вызывая метод clipRect: g.clipRect(260, 250, 200, 100); Затем мы рисуем круг, пересекающий эту область ограничения, в результате чего будет нарисована только та часть круга, которая находится внутри области ограничения: g.fillOval(300, 200, 170, 170); Приложение FontList Наше следующее приложение отображает список шрифтов, доступных в системе. Окно аплета этого приложения, запущенного в среде Microsoft Internet Explorer, работающего в Microsoft Windows NT версии 4.0, показано на рис. 3.10. Рис. 3.10. Окно аплета со списком доступных шрифтов Возможно, этот список покажется вам несколько необычным. В самом деле, давайте заглянем в папку Fonts, которую можно найти в папке Control Panel. Беглого взгляда достаточно для того, чтобы убедиться - список шрифтов, доступных аплету, не совпадает со списком шрифтов, установленных в системе (рис. 3.11). Рис. 3.11. Список шрифтов, установленных в системе Microsoft Windows NT Задавая имена шрифтов в конструкторе класса Font, вы должны использовать имена шрифтов, доступные аплету, а не имена шрифтов, установленных в системе. Наш аплет FontList извлекает и отображает список доступных для него шрифтов. Исходный текст приложения Исходный текст приложения представлен в листинге 3.3. Листинг 3.3. Файл FontList\FontList.java // ========================================================= // Просмотр списка доступных шрифтов // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class FontList extends Applet { // ------------------------------------------------------- // Поля класса // ------------------------------------------------------- Toolkit toolkit; // ссылка на Toolkit String fntlist[]; // список шрифтов int yStart = 20; // координата Y начала области вывода int yStep; // шаг вывода строк с названиями шрифтов // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: FontList\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Получаем ссылку на Toolkit toolkit = Toolkit.getDefaultToolkit(); // Получаем список доступных шрифтов fntlist = toolkit.getFontList(); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем метрики шрифта FontMetrics fm = g.getFontMetrics(); // Устанавливаем шаг вывода строк по вертикали // равным полной высоте символов текущего шрифта yStep = fm.getHeight(); // Устанавливаем новую высоту аплета исходя // из количества элементов в списке шрифтов resize(150, 20 + yStep * fntlist.length); // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выводим в цикле список установленных шрифтов for(int i = 0; i < fntlist.length; i++) { g.drawString(fntlist[i], 10, yStart + yStep * i); } } } В листинге 3.4 вы найдете исходный текст документа HTML, в который встроен наш аплет. Листинг 3.4. Файл FontList\FontList.html FontList

The source. Описание исходного текста Наиболее важными для нас являются методы init и paint. Метод init Процедура извлечения списка доступных шрифтов, использованная в нашем аплете, достаточно проста и выполняется в методе init, как это показано ниже: Toolkit toolkit; // ссылка на Toolkit String fntlist[]; // список шрифтов . . . public void init() { toolkit = Toolkit.getDefaultToolkit(); fntlist = toolkit.getFontList(); } Аплет вызывает статический метод getDefaultToolkit из класса Toolkit и затем, пользуясь полученной ссылкой, извлекает список шрифтов, записывая его в массив fntlist. Для чего еще можно использовать класс Toolkit? Класс Toolkit является абстрактным суперклассом для всех реализаций AWT. Порожденные от него классы используются для привязки различных компонент конкретных реализаций. Создавая свои аплеты, вы будете редко прибегать к услугам этого класса. Однако в нем есть несколько полезных методов, прототипы которых мы перечислим ниже: public abstract class java.awt.Toolkit extends java.lang.Object { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public Toolkit(); // ----------------------------------------------------- // Методы (сокращенный список) // ----------------------------------------------------- . . . // Получение ссылки на Toolkit public static Toolkit getDefaultToolkit(); // Определение текущей цветовой модели, // выбранной в контекст отображения public abstract ColorModel getColorModel(); // Получение списка шрифтов, доступных аплету public abstract String[] getFontList(); // Получение метрик заданного шрифта public abstract FontMetrics getFontMetrics(Font font); // Получение растрового изображения по имени файла public abstract Image getImage(String filename); // Получение растрового изображения по адресу URL public abstract Image getImage(URL url); // Определение разрешения экрана в точках на дюйм public abstract int getScreenResolution(); // Размеры экрана в пикселах public abstract Dimension getScreenSize(); // Подготовка растрового изображения для вывода public abstract boolean prepareImage(Image image, int width, int height, ImageObserver observer); // Синхронизация состояния Toolkit public abstract void sync(); } Наиболее интересны, с нашей точки зрения, методы getFontList, getScreenResolution и getScreenSize, с помощью которых аплет может, соответственно, плучить список шрифтов, определить разрешение и размер экрана. Последние два параметра позволяют сформировать содержимое окна аплета оптимальным образом исходя из объема информации, который может в нем разместиться. Метод paint В методе paint прежде всего мы определяем полную высоту символов шрифта, которая будет использована при выводе строк. Высота шрифта определяется следующим образом: FontMetrics fm = g.getFontMetrics(); yStep = fm.getHeight(); Зная высоту шрифта и количество элементов в списке доступных шрифтов, мы можем изменить размер окна аплета по вертикали таким образом, чтобы в нем поместились все строки. Количество элементов в массиве fntlist равно fntlist. length, а полную высоту шрифта мы только что определили. Для изменения высоты окна аплета мы используем метод resize: resize(150, 20 + yStep * fntlist.length); Далее мы определяем новые размеры окна аплета, закрашиваем фон окна желтым цветом и обводим окно тонкой рамкой черного цвета: Dimension dimAppWndDimension = size(); g.setColor(Color.yellow); g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); g.setColor(Color.black); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Эту операцию мы уже выполняли в предыдущем приложении. Список установленных шрифтов выводится достаточно просто в цикле: for(int i = 0; i < fntlist.length; i++) { g.drawString(fntlist[i], 10, yStart + yStep * i); } Здесь содержимое параметра цикла (переменной i) меняется от 0 до количества элементов в массиве length. Каждая новая строка рисуется со смещением, равным полной высоте символов текущего шрифта, выбранного в контекст отображения. Приложение TextOut До сих пор наши аплеты не получали параметров из документов HTML, в которые мы их встраивали. Конечно, все константы, текстовые строки, адреса URL и другую информацию можно закодировать непосредственно в исходном тексте аплета, однако, очевидно, это очень неудобно. Пользуясь операторами , расположенными в документе HTML сразу после оператора , можно передать аплету произвольное количество параметров, например, в виде текстовых строк: . . . Здесь через параметр NAME оператора передается имя параметра аплета, а через параметр VALUE - значение соответствующего параметра. Как параметр может получить значение параметров? Для получения значения любого параметра аплет должен использовать метод getParameter. В качестве единственного параметра этому методу передается имя параметра аплета в виде строки типа String, например: private String m_ParamName1; private final String PARAM_ ParamName1= "ParamName1"; String param; param = getParameter(PARAM_ParamName1); if (param != null) m_ParamName1 = param; Если вы создаете аплет с помощью системы Java Applet Wizard, то в четвертой диалоговой панели вам предоставляется возможность определить все параметры, передаваемые аплету (рис. 3.12). Рис. 3.12. Определение параметров аплета (список параметров уже заполнен) Первоначально список параметров, отображаемых в четвертой диалоговой панели системы Java Applet Wizard, пуст. Такую панель мы показывали раньше на рис. 2. 11. Для добавления нового параметра сделайте щелчок левой клавишей мыши в столбце Name по свободному полю, отмеченному пунктирным прямоугольником. На месте этого прямоугольника появится поле редактирования, в котором вы должны ввести имя параметра. После ввода сделайте щелчок вне поля, после чего в списке параметров появится новая строка. Создавая проект TextOut, мы выполнили эту операцию для всех параметров, за исключением параметра Font4. Этот параметр мы добавили позже в ручном режиме, когда все файлы проекта уже были созданы. Обратите внимание, что в столбце Member при заполнении списка автоматически появляются имена полей класса, в которые попадут значения параметров. После завершения формирования списка параметров мы заполнили столбцы Def-Value и Description (рис. 3.13). Рис. 3.13. Заполнение столбцов Def-Value и Description Значения из столбца Def-Value будут использованы для инициализации соответствующих полей класса. Что же касается столбца описаний Description, о эта информация может быть извлечена аплетом и проанализирована. Если в документе HTML находится несколько аплетов (что вполне допустимо), другие аплеты также могут получить описание параметров нашего аплета. Какие параметры получает наш аплет и что он делает, кроме получения значения параметров? Через параметры с именами Str1 - Str5 передается пять строк, который аплет отображает в своем окне (рис. 3.14). Рис. 3.14. Отображение строк в окне аплета TextOut Параметры Font1 - Font5 задают имена шрифтов для отображения этих строк. С помощью параметра Type1 можно задать стиль шрифта первой и второй строки, с помощью параметра Type2 - третьей и четвертой, а с помощью параметра Type3 - стиль шрифта для пятой строки. Рассмотрим исходный тексты приложения TextOut. Исходные тексты приложения TextOut Файл исходного текста приложения TextOut представлен в листинге 3.5. Листинг 3.5. Файл TextOut\TextOut.java // ========================================================= // Установка различных шрифтов. // Демонстрация способов передачи параметров в аплет // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class TextOut extends Applet { // ------------------------------------------------------- // Поля класса. // Создаются автоматически для всех параметров аплета // ------------------------------------------------------- private String m_Str1 = "Hello 1"; private String m_Str2 = "Hello 2"; private String m_Str3 = "Hello 3"; private String m_Str4 = "Hello 4"; private String m_Str5 = "Hello 5"; private String m_Font1 = "Arial"; private String m_Font2 = "Courier"; private String m_Font3 = "Times"; private String m_Font4 = "Helvetica"; private String m_Font5 = "Undefined"; private String m_Type1 = "Bold"; private String m_Type2 = "Italic"; private String m_Type3 = "Plain"; // ------------------------------------------------------- // Имена параметров // ------------------------------------------------------- private final String PARAM_Str1 = "Str1"; private final String PARAM_Str2 = "Str2"; private final String PARAM_Str3 = "Str3"; private final String PARAM_Str4 = "Str4"; private final String PARAM_Str5 = "Str5"; private final String PARAM_Font1 = "Font1"; private final String PARAM_Font2 = "Font2"; private final String PARAM_Font3 = "Font3"; private final String PARAM_Font4 = "Font4"; private final String PARAM_Font5 = "Font5"; private final String PARAM_Type1 = "Type1"; private final String PARAM_Type2 = "Type2"; private final String PARAM_Type3 = "Type3"; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: TextOut\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // getParameterInfo // Метод, возвращающий описание параметров // ------------------------------------------------------- public String[][] getParameterInfo() { String[][] info = { { PARAM_Str1, "String", "Text string to write" }, { PARAM_Str2, "String", "Text string to write" }, { PARAM_Str3, "String", "Text string to write" }, { PARAM_Str4, "String", "Text string to write" }, { PARAM_Str5, "String", "Text string to write" }, { PARAM_Font1, "String", "Text font" }, { PARAM_Font2, "String", "Text font" }, { PARAM_Font3, "String", "Text font" }, { PARAM_Font4, "String", "Text font" }, { PARAM_Font5, "String", "Text font" }, { PARAM_Type1, "String", "Font type" }, { PARAM_Type2, "String", "Font type" }, { PARAM_Type3, "String", "Font type" }, }; return info; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Рабочая переменная для получения параметров String param; // Получение параметров и сохранение // их значений в полях класса // Строки, которые будут выведены в окно аплета param = getParameter(PARAM_Str1); if (param != null) m_Str1 = param; param = getParameter(PARAM_Str2); if (param != null) m_Str2 = param; param = getParameter(PARAM_Str3); if (param != null) m_Str3 = param; param = getParameter(PARAM_Str4); if (param != null) m_Str4 = param; param = getParameter(PARAM_Str5); if (param != null) m_Str5 = param; // Шрифты для отображения строк param = getParameter(PARAM_Font1); if (param != null) m_Font1 = param; param = getParameter(PARAM_Font2); if (param != null) m_Font2 = param; param = getParameter(PARAM_Font3); if (param != null) m_Font3 = param; param = getParameter(PARAM_Font4); if (param != null) m_Font4 = param; param = getParameter(PARAM_Font5); if (param != null) m_Font5 = param; // Начертание шрифтов param = getParameter(PARAM_Type1); if (param != null) m_Type1 = param; param = getParameter(PARAM_Type2); if (param != null) m_Type2 = param; param = getParameter(PARAM_Type3); if (param != null) m_Type3 = param; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Начальная координата для вывода по вертикали int yStart = 20; // Текущая координата для вывода строки int yCurrent = 20; // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Получаем стиль шрифта и выбираем шрифт // в соответствии с этим стилем if(m_Type1.equals("Bold")) g.setFont(new Font(m_Font1, Font.BOLD, 25)); else if(m_Type1.equals("Italic")) g.setFont(new Font(m_Font1, Font.ITALIC, 25)); else if(m_Type1.equals("Plain")) g.setFont(new Font(m_Font1, Font.PLAIN, 25)); // Отступ для первой строки yCurrent += yStart; // Рисуем первую строку g.drawString(m_Str1, 10, yCurrent); // Определяем метрики шрифта FontMetrics fm = g.getFontMetrics(); // Устанавливаем новую текущую позицию для // вывода очередной строки yCurrent += fm.getHeight(); // Выбираем шрифт в контекст отображения if(m_Type1.equals("Bold")) g.setFont(new Font(m_Font2, Font.BOLD, 25)); else if(m_Type1.equals("Italic")) g.setFont(new Font(m_Font2, Font.ITALIC, 25)); else if(m_Type1.equals("Plain")) g.setFont(new Font(m_Font2, Font.PLAIN, 25)); // Рисуем вторую строку g.drawString(m_Str2, 10, yCurrent); // Устанавливаем новую текущую позицию для // вывода очередной строки fm = g.getFontMetrics(); yCurrent += fm.getHeight(); // Выбираем шрифт в контекст отображения if(m_Type2.equals("Bold")) g.setFont(new Font(m_Font3, Font.BOLD, 25)); else if(m_Type2.equals("Italic")) g.setFont(new Font(m_Font3, Font.ITALIC, 25)); else if(m_Type2.equals("Plain")) g.setFont(new Font(m_Font3, Font.PLAIN, 25)); // Рисуем третью строку g.drawString(m_Str3, 10, yCurrent); // Устанавливаем новую текущую позицию для // вывода очередной строки fm = g.getFontMetrics(); yCurrent += fm.getHeight(); // Выбираем шрифт в контекст отображения if(m_Type2.equals("Bold")) g.setFont(new Font(m_Font4, Font.BOLD, 25)); else if(m_Type2.equals("Italic")) g.setFont(new Font(m_Font4, Font.ITALIC, 25)); else if(m_Type2.equals("Plain")) g.setFont(new Font(m_Font4, Font.PLAIN, 25)); // Рисуем четвертую строку g.drawString(m_Str4, 10, yCurrent); // Устанавливаем новую текущую позицию для // вывода очередной строки fm = g.getFontMetrics(); yCurrent += fm.getHeight(); // Выбираем шрифт в контекст отображения if(m_Type3.equals("Bold")) g.setFont(new Font(m_Font5, Font.BOLD, 25)); else if(m_Type3.equals("Italic")) g.setFont(new Font(m_Font5, Font.ITALIC, 25)); else if(m_Type3.equals("Plain")) g.setFont(new Font(m_Font5, Font.PLAIN, 25)); // Рисуем пятую строку g.drawString(m_Str5, 10, yCurrent); } } Исходный текст документа HTML, в который встроен аплет TextOut, приведен в листинге 3.6. Листинг 3.6. Файл TextOut\TextOut.html TextOut

The source. Описание исходных текстов Если при создании шаблона аплета с помощью системы Java Applet Wizard вы указываете, что аплету передаются параметры и определяете их список, система Java Applet Wizard организует для вас прием и хранение параметров. Поэтому обработка параметров аплета не отнимет у вас много сил. Какие строки добавляются системой Java Applet Wizard для обработки параметров? Поля класса TextOut Прежде всего, создаются поля класса для хранения значений параметров: private String m_Str1 = "Hello 1"; . . . private String m_Str5 = "Hello 5"; private String m_Font1 = "Arial"; . . . private String m_Font5 = "Undefined"; private String m_Type1 = "Bold"; private String m_Type2 = "Italic"; private String m_Type3 = "Plain"; Поля инициализируются значениями по умолчанию, которые вы ввели при заполнении таблицы, показанной на рис. 3.13. Далее в классе определяются поля с названиями параметров: private final String PARAM_Str1 = "Str1"; . . . private final String PARAM_Str5 = "Str5"; private final String PARAM_Font1 = "Font1"; . . . private final String PARAM_Font5 = "Font5"; private final String PARAM_Type1 = "Type1"; private final String PARAM_Type2 = "Type2"; private final String PARAM_Type3 = "Type3"; Названия параметров будут нужны для извлечения значений параметров методом getParameter класса Applet. Метод getParameterInfo Система Java Applet Wizard переопределяет метод getParameterInfo, который возвращает ссылку на массив массивов с описаниями параметров: public String[][] getParameterInfo() { String[][] info = { { PARAM_Str1, "String", "Text string to write" }, . . . { PARAM_Str5, "String", "Text string to write" }, { PARAM_Font1, "String", "Text font" }, . . . { PARAM_Font5, "String", "Text font" }, { PARAM_Type1, "String", "Font type" }, { PARAM_Type2, "String", "Font type" }, { PARAM_Type3, "String", "Font type" }, }; return info; } Как мы уже говорили, эта информация может использоваться другими аплетами, размещенными в том же документе HTML и работающими одновременно с нашим аплетом. Метод init При инициализации аплета метод init читает все параметры и записывает их значения в соответствующие поля класса, как это показано ниже: public void init() { String param; param = getParameter(PARAM_Str1); if (param != null) m_Str1 = param; . . . param = getParameter(PARAM_Str5); if (param != null) m_Str5 = param; // Шрифты для отображения строк param = getParameter(PARAM_Font1); if (param != null) m_Font1 = param; . . . param = getParameter(PARAM_Font5); if (param != null) m_Font5 = param; // Начертание шрифтов param = getParameter(PARAM_Type1); if (param != null) m_Type1 = param; . . . param = getParameter(PARAM_Type3); if (param != null) m_Type3 = param; } Здесь все просто. Метод init по очереди получает значения параметров методом getParameter, которому в качестве параметра передается имя параметра аплета. Полученное значение сохраняется в рабочей переменной param и, если оно отлично от значения null, сохраняется в соответствующем поле класса. Метод paint После закрашивания фона желтым цветом и рисования вокруг окна аплета черной рамки метод paint анализирует значение параметра m_Type1 и выбирает в контекст отображения шрифт для рисования первой строки: if(m_Type1.equals("Bold")) g.setFont(new Font(m_Font1, Font.BOLD, 25)); else if(m_Type1.equals("Italic")) g.setFont(new Font(m_Font1, Font.ITALIC, 25)); else if(m_Type1.equals("Plain")) g.setFont(new Font(m_Font1, Font.PLAIN, 25)); Для сравнения строк класса String мы используем метод equals, который возвращает значение true при совпадении с заданной строкой и false в противном случае. Методу выбора шрифта setFont мы передаем объект класса Font, созданный конструктором. Конструктор получает в качетсве первого параметра содержимое поля класса m_Font1, которое соответствует значению параметра аплета с именем Font1. Значение второго параметра (стиль шрифта) выбирается исходя из значения параметра аплета с именем m_Type1. Здесь мы указываем константы, определенные в классе Font. И, наконец, третий параметр конструктора класса Font задает размер символов шрифта, равный 25 пикселам. После выбора шрифта мы выполняем отступ от верхней границы окна и рисуем первую строку в позиции (0, yCurrent): yCurrent += yStart; g.drawString(m_Str1, 10, yCurrent); На следующем этапе метод paint получает метрику только что выбранного шритфа и увеличивает текущую позицию yCurrent на величину полной высоты символов шрифта, полученную с помощью метода getHeight: FontMetrics fm = g.getFontMetrics(); yCurrent += fm.getHeight(); Далее эта же процедура повторяется для остальный четырех отображаемых в окне аплета строк. Экспериментируя с аплетом, попробуйте изменить параметры, передаваемые аплету в документе HTML. Укажите, например, несуществующий шрифт и посмотрите, какой шрифт будет выбран навигатором для отображения. 4 ОБРАБОТКА СОБЫТИЙ От аплетов Java было бы немного толку, если бы они не умели обрабатывать информацию, поступающую от мыши и клавиатуры. К счастью, такая обработка предусмотрена и она выполняется достаточно просто. Когда пользователь выполняет операции с мышью или клавиатурой в окне аплета, возникают события, которые передаются соответствующим методам класса Applet. Переопределяя эти методы, вы можете организовать обработку событий, возникающих от мыши или клавиатуры. Если вы создавали приложения для операционной системы Microsoft Windows, здесь для вас нет ничего нового - вспомните, как вы обрабатывали сообщение WM_LBUTTONDOWN или WM_CHAR. Когда пользователь выполнял действие с мышью или клавиатурой в окне приложения, функция этого окна получала соответствующее сообщение. Методы класса Applet, обрабатывающие события от мыши и клавиатуры, являются аналогами обработчиков указанных сообщений. Заметим, что аплеты имеют дело только с левой клавишей мыши. В текущей версии Java вы не можете никаким образом задействовать в аплете правую или среднюю клавишу мыши. Как обрабатываются события Когда возникает событие, управление получает метод handleEvent из класса Component. Класс Applet является дочерним по отношению к классу Component. Прототип метода handleEvent мы привели ниже: public boolean handleEvent(Event evt); В качестве параметра методу handleEvent передается объект класса Event, который содержит всю информацию о событии. По содержимому полей класса Event вы можете определить координаты курсора мыши в момент, когда пользователь нажал клавишу, отличить одинарный щелчок от двойного и так далее. Ниже мы привели список полей класса Event, которые вы можете проанализировать: ПолеОписаниеpublic Object arg;Произвольный аргумент события, значение которого зависит от типа событияpublic int clickCount; Это поле имеет значение только для события с типом MOUSE_DOWN и содержит количество нажатий на клавишу мыши. Если пользователь сделал двойной щелчок мышью, в это поле будет записано значение 2public Event evt;Следующее событие в связанном спискеpublic int id; Тип события. Ниже мы перечислим возможные значения для этого поляpublic int key;Код нажатой клавиши (только для события, созданного при выполнении пользователем операции с клавиатурой)public int modifiers;Состояние клавиш модификации , , public Object target;Компонент, в котором произошло событиеpublic long when;Время, когда произошло событиеpublic int x; Координата по оси Xpublic int y;Координата по оси Y Поле id (тип события) может содержать следующие значения: ЗначениеТип событияACTION_EVENTПользователь хочет, чтобы произошло некоторое событиеGOT_FOCUSКомпонент (в нашем случае окно аплета) получил фокус ввода. О фокусе ввода вы узнаете из раздела, посвященного работе с клавиатурой KEY_ACTION Пользователь нажал клавишу типа “Action”KEY_ACTION_RELEASE Пользователь отпустил клавишу типа “Action”KEY_PRESSПользователь нажал обычную клавишуKEY_RELEASEПользователь отпустил обычную клавишуLIST_DESELECTОтмена выделения элемента в спискеLIST_SELECTВыделение элемента в спискеLOAD_FILE Загрузка файлаLOST_FOCUSКомпонент потерял фокус вводаMOUSE_DOWNПользователь нажал клавишу мышиMOUSE_DRAGПользователь нажал клавишу мыши и начал выполнять перемещение курсора мышиMOUSE_ENTERКурсор мыши вошел в область окна аплета MOUSE_EXITКурсор мыши покинул область окна аплетаMOUSE_MOVEПользователь начал выполнять перемещение курсора мыши, не нажимая клавишу мышиMOUSE_UPПользователь отпустил клавишу мышиSAVE_FILEСохранение файлаSCROLL_ABSOLUTEПользователь переместил движок полосы просмотра в новую позициюSCROLL_LINE_DOWNПользователь выполнил над полосой просмотра операцию сдвига на одну строку вниз SCROLL_LINE_UPПользователь выполнил над полосой просмотра операцию сдвига на одну строку вверхSCROLL_PAGE_DOWNПользователь выполнил над полосой просмотра операцию сдвига на одну страницу внизSCROLL_PAGE_UPПользователь выполнил над полосой просмотра операцию сдвига на одну страницувверхWINDOW_DEICONIFY Пользователь запросил операцию восстановления нормального размера окна после его минимизацииWINDOW_DESTROYПользователь собирается удалить окноWINDOW_EXPOSE Окно будет отображеноWINDOW_ICONIFYОкно будет минимизированоWINDOW_MOVEDОкно будет перемещено Если событие связано с клавиатурой (тип события KEY_ACTION или KEY_ACTION_RELEASE), в поле key может находиться одно из следующих значений: ЗначениеКлавишаDOWNКлавиша перемещения курсора внизENDF1F2F3F4 F5F6F7F8F9F10F11F12HOMELEFTКлавиша перемещения курсора влевоPGDNPGUPRIGHTКлавиша перемещения курсора вправоUPКлавиша перемещения курсора вниз Могут быть указаны следующие маски для поля модификаторов modifiers: Значение маскиОписаниеALT_MASKБыла нажата клавиша META_MASK Была нажата мета-клавиша (клавиша для ввода диактрических символов)CTRL_MASKБыла нажата клавиша SHIFT_MASKБыла нажата клавиша Ваше приложение может переопределить метод handleEvent и обрабатывать события самостоятельно, однако есть более простой путь. Обработчик этого метода, который используется по умолчанию, вызывает несколько методов, которые более удобны в использовании, в частности, при обработке событий от мыши или клавиатуры. События от мыши В этом разделе мы рассмотрим события, которые возникают в результате того, что пользователь выполняет в окне аплета операции с мышью. Это такие операции, как нажатие и отпускание клавиши мыши, перемещение курсора мыши в окне аплета с нажатой или отпущенной клавишей, перемещение курсора мыши в окно аплета и удаление этого курсора из окна аплета. Все перечисленные ниже методы должны вернуть значение true, если обработка события выполнена успешно и дальнейшая обработка не требуется. Если же методы вернут значение fasle, событие будет обработано методом из базового класса, то есть для него будет выполнена обработка, принятая по умолчанию. Программисты, создававшие приложения для операционной системы Microsoft Windows, могут найти здесь аналогию с вызовом функции DefWindowProc, которая выполняет обработку сообщений, принятую по умолчанию. Нажатие клавиши мыши Переопределив метод mouseDown, вы сможете отслеживать нажатия клавиши мыши. Прототип этого метода приведен ниже: public boolean mouseDown(Event evt, int x, int y); Через параметр evt методу передается ссылка на объект Event, с помощью которой метод может получить полную информацию о событии. Анализируя содержимое параметров x и y, приложение может определить координаты курсора на момент возникновения события. Заметим, что для отслеживания двойного щелчка мыши не предусмотрено никакого отдельного метода. Однако анализируя содержимое поля clickCount переменной evt, вы можете определить кратность щелчка мыши: if(evt.clickCount > 1) // Двойной щелчок showStatus("Mouse Double Click"); else // Одинарный щелчок showStatus("Mouse Down"); Отпускание клавиши мыши При отпускании клавиши мыши управление получает метод mouseUp: public boolean mouseUp(Event evt, int x, int y); Анализируя параметры x и y, вы можете определить координаты точки, в которой пользователь отпустил клавишу мыши. Перемещение курсора мыши Когда пользователь перемещает курсор мыши над окном аплета, в процессе перемещения происходит вызов метода mouseMove: public boolean mouseMove(Event evt, int x, int y); Через переменные x и y передаются текущие координаты курсора мыши. Выполнение операции Drag and Drop Операция Drag and Drop выполняется следующим образом: пользователь нажимает клавишу мыши и, не отпуская ее, начинает перемещать курсор мыши. При этом происходит вызов метода mouseDrag: public boolean mouseDrag(Event evt, int x, int y); Через переменные x и y передаются текущие координаты курсора мыши. Метод mouseDrag вызывается даже в том случае, если в процессе перемещения курсор вышел за пределы окна аплета. Вход курсора мыши в область окна аплета Метод mouseEnter получает управление, когда курсор мыши в процессе перемещения по экрану попадает в область окна аплета: public boolean mouseEnter(Event evt, int x, int y); Вы можете использовать этот метод для активизации аплета, на который указывает курсор мыши. Выход курсора мыши из области окна аплета Метод mouseExit вызывается при покидании куросром окна аплета: public boolean mouseExit(Event evt, int x, int y); Если пользователь убрал курсор из окна аплета, активизированного методом mouseEnter, то метод mouseExit может переключить аплет в пассивное состояние. Приложение MouseClick Аплет MouseClick демонстрирует обработку событий, поступающих от мыши. Когда мы создавали проект этого аплета, то в третьей диалоговой панели системы Java Applet Wizard включили три переключателя в поле Which mouse event handlers would you like added (рис. 4.1). Рис. 4.1. Включение обработчиков событий от мыши В результате в исходный текст приложения были добавлены перечисленные выше методы, обрабатывающие события, создаваемые мышью. Мы изменили эти методы таким образом, чтобы в ответ на различные действия, выполняемые пользователем с помощью мыши, аплет реагировал соответствующим образом: Действие пользователяРеакция аплетаПеремещение курсора мыши при отжатой клавише ИгнорированиеПеремещение курсора мыши при нажатой клавишеВ строку состояния записывается текстовая строка Mouse DragНажатие клавиши мышиВ месте расположения курсора выводятся текущие координаты курсора мыши. Дополнительно в строку состояния записывается текстовая строка Mouse DownОтжатие клавиши мышиВ строку состояния записывается текстовая строка Mouse UpКурсор мыши входит в область окна аплетаВ строку состояния записывается текстовая строка Mouse pointer enters applet's windowКурсор мыши выходит из области окна аплетаВ строку состояния записывается текстовая строка Mouse pointer leaves applet's window Внешний вид окна аплета, в котором отображаются координаты курсора, показан на рис. 4.2. Рис. 4.2. Внешний вид окна аплета MouseClick, в котором отображаются координаты курсора Исходные тексты приложения Файл исходного текста приложения MouseClick представлен в листинге 4.1. Листинг 4.1. Файл MouseClick\MouseClick.java // ========================================================= // Обработка событий от мыши // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class MouseClick extends Applet { // Текущие координаты курсора при нажатии на // кнопку мыши Dimension dimMouseCursor; // Временная переменная для хранения события Event ev; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: MouseClick\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Отображаем текущие координаты курсора мыши // в точке, где находится этот курсор g.drawString("(" + ev.x + "," + ev.y + ")", ev.x, ev.y); } // ------------------------------------------------------- // mouseDown // Обработка щелчка кнопкой мыши // ------------------------------------------------------- public boolean mouseDown(Event evt, int x, int y) { // Определяем и сохраняем текущие координаты // курсора мыши dimMouseCursor = new Dimension(x, y); // Сохраняем событие во временной переменной ev = evt; // Если количествао щелчков больше 1, считаем что // сделан двойной щелчок if(evt.clickCount > 1) // Выводим сообщение о двойном щелчке showStatus("Mouse Double Click"); // Сделан одиночный щелчок else // Выводим сообщение о простом щелчке showStatus("Mouse Down"); // Перерисовываем окно аплета repaint(); // Возвращаем значение true при успешной // обработке события return true; } // ------------------------------------------------------- // mouseUp // Отпускание клавиши мыши // ------------------------------------------------------- public boolean mouseUp(Event evt, int x, int y) { // Выводим сообщение в строке состояния showStatus("Mouse Up"); return true; } // ------------------------------------------------------- // mouseDrag // Перемещение курсора мыши при нажатой клавише // ------------------------------------------------------- public boolean mouseDrag(Event evt, int x, int y) { // Выводим сообщение в строке состояния showStatus("Mouse Drag"); return true; } // ------------------------------------------------------- // mouseMove // Перемещение курсора мыши при отжатой клавише // ------------------------------------------------------- public boolean mouseMove(Event evt, int x, int y) { return true; } // ------------------------------------------------------- // mouseEnter // Курсор мыши вошел в область окна аплета // ------------------------------------------------------- public boolean mouseEnter(Event evt, int x, int y) { // Выводим сообщение в строке состояния showStatus("Mouse pointer enters applet's window"); return true; } // ------------------------------------------------------- // mouseExit // Курсор мыши покинул область окна аплета // ------------------------------------------------------- public boolean mouseExit(Event evt, int x, int y) { // Выводим сообщение в строке состояния showStatus("Mouse pointer leaves applet's window"); return true; } } Исходный текст документа HTML, созданного для нашего аплета системой Java Applet Wizard, приведен в листинге 4.2. Листинг 4.2. Файл MouseClick\MouseClick.html MouseClick

The source. Описание исходного текста В исходном тексте класса MouseClick мы определили поля класса с именами dimMouseCursor и ev: Dimension dimMouseCursor; Event ev; Первое из них предназначено для хранения координат курсора в момент возникновения события, а второе - хранит ссылку на это событие. Метод getAppletInfo Метод getAppletInfo ничем не отличается от аналогичных методов в предыдущих приложениях. Метод paint В начале своей работы метод paint определяет текущие размеры окна аплета, закрашивает это окно в желтый цвет и рисует вокруг него тонкую рамку черного цвета. Все это делается исключительно для того чтобы выделить аплет в документе HTML и обозначить его границы. Далее метод paint отображает текущие координаты курсора мыши, взяв их из переменной ev: g.drawString("(" + ev.x + "," + ev.y + ")", ev.x, ev.y); Метод mouseDown Когда пользователь делает щелчок левой клавишей мыши (напомним, что Java не работает с другими клавишами мыши), управление получает метод mouseDown. Этот метод, переопределенный в нашем приложении, прежде всего сохраняет текущие координаты курсора мыши в переменной dimMouseCursor класса Dimension: dimMouseCursor = new Dimension(x, y); Событие, которое передается методу mouseDown через первый параметр, сохраняется в переменной ev: ev = evt; Далее метод mouseDown проверяет поле clickCount параметра evt: if(evt.clickCount > 1) showStatus("Mouse Double Click"); else showStatus("Mouse Down"); В это поле записывается кратность щелчка мыши. Если пользователь сделал двойной щелчок, в строке состояния отображается текстовая строка Mouse Double Click, а если одинарный - строка Mouse Down. Обратите внимание на метод showStatus. Этот метод позволяет аплету отобразить любую текстовую строку в строке состояния навигатора, поэтому он часто используется для отладки или выдачи текущей информации о состоянии аплета. Заметим, однако, что в документе HTML может располагаться несколько разных аплетов, а строка состояния навигатора только одна. Поэтому сообщения от разных аплетов могут перекрывать друг друга, в результате чего в строке состояния появится только то сообщение, которое было записано туда последним. После записи сообщения в строку состояния метод mouseDown перерисывывает окно аплета, вызывая для этого метод repaint: repaint(); В результате вызова метода repaint происходит инициирование вызова метода paint, выполняющего перерисовку содержимого окна аплета. Однако не следует думать, будто метод repaint просто вызывает метод paint. Метод paint вызывается интерпретатором Java асинхронно по отношению к методу repaint в подходящий момент времени. В последней строке метод mouseDown возвращает значение true: return true; Этим он сообщает, что обработка события завершена и это событие не нужно передавать обработчику из базового класса. Методы mouseUp, mouseDrag, mouseEnter, mouseExit Обработчики методов mouseUp, mouseDrag, mouseEnter и mouseExit выглядят одинаково: public boolean mouseUp(Event evt, int x, int y) { // Выводим сообщение в строке состояния showStatus("Mouse Up"); return true; } Пользуясь методом showStatus, эти методы записывают соответствующее сообщение в строку состояния и возвращают значение true. Метод mouseMove Метод mouseMove выглядит следующим образом: public boolean mouseMove(Event evt, int x, int y) { return true; } Он ничего не делает, кроме того что возвращает значение true, блокируя обработку события, выполняемую в базовом классе. Приложение LineDraw Следующее приложение тоже работает с мышью. В его окне вы можете рисовать прямые линии черного цвета (рис. 4.3). Рис. 4.3. Аплет, в окне которого можно рисовать прямые линии Для того чтобы нарисовать линию в окне аплета LineDraw, вы должны установить курсор в начальную точку, нажать клавишу мыши и затем, не отпуская ее, переместить курсор в конечную точку. После отпускания клавиши мыши координаты линии будут сохранены аплетом в массиве, после чего произойдет перерисовка окна аплета. По мере того как вы будете рисовать линии, аплет будет заполнять массив с координатами линий. Каждый раз, когда окно аплета будет перерисовываться, метод paint перерисует все линии заново, пользуясь координатами, сохраненными в массиве. Для того чтобы стереть содержимое окна аплета, вам достаточно сделать двойной щелчок в его окне. При этом из массива координать линий будут удалены все элементы. Исходные тексты приложения Исходный текст аплета LineDraw приведен в листинге 4.3. Листинг 4.3. Файл LineDraw\LineDraw.java // ========================================================= // Рисование прямых линий // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // Добавляем для класса Vector import java.util.*; public class LineDraw extends Applet { // Координаты курсора при нажатии кнопки мыши Dimension dmDown; // Координаты курсора при отжатии кнопки мыши Dimension dmUp; // Предыдущие координаты конца линии Dimension dmPrev; // Признак включения режима рисования boolean bDrawing; // Массив координат линий Vector lines; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: LineDraw\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Сброс признака рисования bDrawing = false; // Создание массива для хранения координат линий lines = new Vector(); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем в цикле линии, координаты которых // хранятся в массиве lines for (int i=0; i < lines.size(); i++) { // Получаем координаты очередной линии Rectangle p = (Rectangle)lines.elementAt(i); // Рисуем линию g.drawLine( p.width, p.height, p.x, p.y); } // Сбрасываем режим рисования bDrawing = false; } // ------------------------------------------------------- // mouseDown // Обработка щелчка кнопкой мыши // ------------------------------------------------------- public boolean mouseDown(Event evt, int x, int y) { // Если количествао щелчков больше 1, считаем что // сделан двойной щелчок if(evt.clickCount > 1) { // Удаляем все строки из массива lines lines.removeAllElements(); // Перерисовываем окно аплета repaint(); return true; } // Сохраняем текущие координаты начала линии dmDown = new Dimension(x, y); // В начале процесса рисования линии устанавливаем // предыдущие координаты конца линии равными // координатам начала линии dmPrev = new Dimension(x, y); // Отключаем режим рисования bDrawing = false; return true; } // ------------------------------------------------------- // mouseUp // Отпускание клавиши мыши // ------------------------------------------------------- public boolean mouseUp(Event evt, int x, int y) { // Проверяем, включен ли режим рисования if(bDrawing) { // Если режим рисования включен, добавляем // новый элемент в массив lines // Сохраняем координаты конца линии dmUp = new Dimension(x, y); // Добавляем линию в массив lines.addElement( new Rectangle(dmDown.width, dmDown.height, x, y)); // Перерисовываем окно аплета repaint(); // Отключаем режим рисования bDrawing = false; } return true; } // ------------------------------------------------------- // mouseDrag // Перемещение курсора мыши при нажатой клавише // ------------------------------------------------------- public boolean mouseDrag(Event evt, int x, int y) { // Получаем контекст отображения для окна аплета Graphics g = getGraphics(); // Включаем режим рисования bDrawing = true; // Закрашиваем предыдущую линию цветом фона // (то есть стираем ее) g.setColor(Color.yellow); g.drawLine(dmDown.width, dmDown.height, dmPrev.width, dmPrev.height); // Рисуем новую линию черным цветом g.setColor(Color.black); g.drawLine(dmDown.width, dmDown.height, x, y); // Сохраняем координаты предыдущей линии, // чтобы стереть ее в следующий раз dmPrev = new Dimension(x, y); return true; } // ------------------------------------------------------- // mouseMove // Перемещение курсора мыши при отжатой клавише // ------------------------------------------------------- public boolean mouseMove(Event evt, int x, int y) { // Отключаем режим рисования bDrawing = false; return true; } } Исходный текст документа HTML, созданного для аплета LineDraw, вы найдете в листинге 4.4. Листинг 4.4. Файл LineDraw\LineDraw.html LineDraw

The source. Описание исходного текста В нашем аплете мы будем создавать объект класса Vector, который является массивом с динамически изменяемым размером. Этот класс имеет полное имя java. util.Vector, поэтому мы подключаем соответствующую библиотеку классов: import java.util.*; Поля класса LineDraw В нашем классе мы определили несколько полей, предназначенных для хранения текущих координат рисуемых линий. В переменную dmDown класса Dimension записываются координаты курсора на момент нажатия клавиши мыши. Если пользователь нажал клавишу мыши для того чтобы приступить к рисованию линии, это будет координатами начала линии. Когда пользователь отпускает клавишу мыши, координаты записываются в переменную dmUp. В процессе рисования линии метод mouseDrag стирает ранее нарисованную линию и рисует новую. Координаты конца старой линии хранятся в переменной dmPrev. Переменная bDrawing типа boolean хранит текущее состояние аплета. Когда аплет находится в состоянии рисования линии, в эту переменную записывается значение true, а когда нет - значение false. И, наконец, переменная lines типа Vector является динамическим массивом, в котором хранятся координаты нарисованных линий. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете и не имеет никаких особенностей. Метод init Метод init сбрасывает признак рисования, записывая в поле bDrawing значение false, а также создает новый динамический массив в виде объекта класса Vector: public void init() { bDrawing = false; lines = new Vector(); } Метод paint После обычной для наших аплетов раскраски фона и рисования рамки метод paint перебирает в цикле все элементы массива lines, рисуя линии: for (int i=0; i < lines.size(); i++) { Rectangle p = (Rectangle)lines.elementAt(i); g.drawLine(p.width, p.height, p.x, p.y); } Для объектов класса Vector можно использовать метода size, возвращающий количество элементов в массиве, чем мы воспользовались для проверки условия выхода из цикла. Чтобы извлечь элемент массива по его номеру, мы воспользовались методом elementAt, передав ему через единственный параметр номер извлекаемого элемента. Так как в массиве хранятся объекты класса Rectangle, перед инициализацией ссылки p мы выполняем преобразование типов. Перед завершением работы метод paint сбрасывает признак рисования, записывая в поле bDrawing значение false: bDrawing = false; Метод mouseDown В начале своей работы метод mouseDown определяет, был ли сделан одинарный щелчок клавишей мыши, или двойной. Если был сделан двойной щелчок мышью, метод удаляет все элементы из массива list, а затем перерисовывает окно аплета, вызывая метод repaint: lines.removeAllElements(); repaint(); После перерисовки окно аплета очищается от линий. Если же был сделан одинарный щелчок клавишей мыши, метод mouseDown сохраняет текущие координаты курсора в переменных dmDown и dmPrev, а затем сбрасывает признак рисования: dmDown = new Dimension(x, y); dmPrev = new Dimension(x, y); bDrawing = false; Метод mouseUp Когда пользователь отпускает клавишу мыши, вызывается метод mouseUp. В его задачу входит сохранение текущих координат курсора мыши в поле dmUp, а также добавление нового элемента в массив lines, как это показано ниже: dmUp = new Dimension(x, y); lines.addElement( new Rectangle(dmDown.width, dmDown.height, x, y)); repaint(); После добавления элемента в массив метод mouseUp инициирует перерисовку окна аплета, вызывая для этого метод repaint. Заметим, что в качестве координат начала линии мы записываем в элемент массива координаты точки, где в последний раз пользователь нажимал курсор мыши. В качестве координат конца линии используются текущие координаты курсора на момент отпускания клавиши мыши. Метод mouseDrag До сих пор наши аплеты выполняли рисование только в методе paint, и так поступают большинство аплетов. Однако наш аплет должен рисовать линии во время перемещения курсора мыши, так как в противном случае пользователю не будет видно, как пройдет рисуемая линия. Для того чтобы нарисовать что-либо в окне аплета, наобходимо получить контекст отображения. Методу paint этот контекст передается через парметр как объект класса Graphics. Если же вы собираетесь рисовать в другом методе, отличном от paint, необходимо получить контекст отображения, например, так: Graphics g = getGraphics(); После получения контекста отображения и включения режима рисования (записью в переменную bDrawing значения true) метод mouseDrag стирает линию, которая была нарисована ранее, в процессе предыдущего вызова этого же метода: g.setColor(Color.yellow); g.drawLine(dmDown.width, dmDown.height, dmPrev.width, dmPrev.height); Для стирания линии мы рисуем ее на том же месте с использованием цвета, совпадающего с цветом фона. Далее метод mouseDrag рисует новую линию черного цвета, соединяя точку, в которой была нажата клавиша мыши, с точкой текущего расположения курсора мыши: g.setColor(Color.black); g.drawLine(dmDown.width, dmDown.height, x, y); После рисования линии координаты ее конца сохраняются в поле dmPrev для стирания этой линии при следующем вызове метода mouseDrag: dmPrev = new Dimension(x, y); return true; Метод mouseMove Метод mouseMove не делает ничего, за исключением того, что он отключает режим рисования. Таким образом, простое перемещение курсора мыши над окном аплета не приводит к рисованию линий. События от клавиатуры Аплет может обрабатывать события, создаваемые клавиатурой. Например, он может реагировать на функциональные клавиши или на клавиши ускоренного выбора функций. Для того чтобы обработать события от клавиатуры, ваш аплет должен переопределить методы keyDown и keyUp: public boolean keyDown(Event evt, int nKey) { . . . } public boolean keyUp(Event evt, int nKey) { . . . } В качестве первого параметра этим методам передается объект типа Event, о полях которого мы рассказывали в разделе “Как обрабатываются события” этой главы. Наибольший интерес представляют поля объекта evt с именами key и modifiers. Через них передается, соответственно, код нажатой клавиши и код модификации. Возможные значения для этих полей вы найдете в только что указанном разделе. Второй параметр методов keyDown и keyUp с именем nKey дублирует поле key объекта evt. Приложение KeyCode Для демонстрации методов обработки клавиатурных событий мы подготовили аплет KeyCode. В его окне отображаются символы, соответствующие нажимаемым клавишам, код соответствующих клавиш и коды модификации (рис. 4.4). Рис. 4.4. Окно аплета KeyCode Аплет KeyCode интересен тем, что в процессе отображения новые строки появляются в верхней части окна, а старые сдвигаются вниз после отпускания клавиши. Таким образом, мы организовали свертку в окне аплета. Прежде чем нажимать клавиши, вы должны передать фокус вода окну аплета. Это можно сделать, щелкнув в окне левой клавишей мыши. Фокус ввода - это атрибут, который присваивается окну, обрабатывающему ввод от клавиатуры. Так как клавиатура одна, а аплетов и других активных окон на экране может быть много, необходим механизм, позволяющий определить, в какое окно направялются события, создаваемые клавиатурой. Такой механизм и обеспечивается атрибутом фокуса ввода. Исходные тексты приложения KeyCode Файл исходного текста приложения KeyCode приведен в листинге 4.5. Листинг 4.5. Файл KeyCode\KeyCode.java // ========================================================= // Просмотр кодов клавиш // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class KeyCode extends Applet { // Высота символов текущего шрифта int yHeight; // Текущие размеры окна аплета Dimension dimAppWndDimension; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: KeyCode\r\n" + "Author: Alexandr Frolov\r\n" + "WWW: http://www.glasnet.ru/~frolov" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Получаем контекст отображения Graphics g = getGraphics(); // Определяем метрики текущего шрифта FontMetrics fm = g.getFontMetrics(); // Сохраняем полную высоту символов шрифта yHeight = fm.getHeight(); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // keyDown // Вызывается, когда пользователь нажимает на клавишу // ------------------------------------------------------- public boolean keyDown(Event evt, int nKey) { // Массив для преобразования кода символа в сивол char[] chKey; // Временная строка String s; // Создаем массив из одного элемента chKey = new char[1]; // Записыаем в него код нажатой клавиши chKey[0] = (char)nKey; // Преобразуем в строку s = new String(chKey); // Получаем контекст отображения Graphics g = getGraphics(); // Выбираем черный цвет для рисования g.setColor(Color.black); // Рисуем символ, соответствующий нажатой клавише g.drawString(s + " ", 10, 10); // Рисуем код клавиши g.drawString( " -> key: " + evt.key, 20, 10); // Рисуем код модификации g.drawString(" mod: " + evt.modifiers, 100, 10); return true; } // ------------------------------------------------------- // keyUp // Вызывается, когда пользователь отпускает клавишу // ------------------------------------------------------- public boolean keyUp(Event evt, int nKey) { // Получаем контекст отображения Graphics g = getGraphics(); // Выполняем свертку нижней области окна g.copyArea(0, 1, dimAppWndDimension.width - 1, dimAppWndDimension.height - yHeight - 5, 0, yHeight + 1); // Закрашиваем область ввода желтым цветом g.setColor(Color.yellow); g.fillRect(1, 1, dimAppWndDimension.width - 2, yHeight + 1); return true; } } В листинге 4.6 вы найдете исходный текст документа HTML, в который встроен наш аплет. Листинг 4.6. Файл KeyCode\KeyCode.html KeyCode

The source. Описание исходного текста Наш аплет определяет несколько полей в своем классе и переопределяет несколько методов базового класса. Поля класса KeyCode Поле yHeight используется для хранения полной высоты символов текущего шрифта, выбранного в контекст отображения окна аплета. Эта величина нужна для определения шага свертки окна. В поле dimAppWndDimension типа Dimension хранятся текущие размеры окна аплета. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете и не имеет никаких особенностей. Метод init Этот метод получает контекст отображения, однако не для рисования, а для определения метрик шрифта: Graphics g = getGraphics(); FontMetrics fm = g.getFontMetrics(); В переменную yHeight заносится полная высота символов текущего шрифта: yHeight = fm.getHeight(); Метод paint Метод paint закрашивает окно аплета желтым цветом и обводит его рамкой. Никакой другой работы этот метод не выполняет. Метод keyDown Когда пользователь нажимает клавишу, управление передается методу keyDown. Обработчик метода keyDown преобразует код нажатой клавиши nKey в текстовую строку типа String и затем отображает эту строку и содержимое двух полей объекта evt в окне аплета. Преобразование выполняется в два приема. Вначале код символа, имеющий тип int, преобразуется к типу char и записывается в ячейку массива типа char[], как это показано ниже: char[] chKey; String s; chKey = new char[1]; chKey[0] = (char)nKey; Затем этот массив, состоящий только из одного элемента, преобразуется в текстовую строку: s = new String(chKey); Далее метод ketDown получает контекст отображения, устанавливает в нем черный цвет и рисует в верхней части окна параметры клавиатурного события: Graphics g = getGraphics(); g.setColor(Color.black); g.drawString(s + " ", 10, 10); g.drawString(" -> key: " + evt.key, 20, 10); g.drawString(" mod: " + evt.modifiers, 100, 10); Метод keyUp Метод keyUp получает управление, когда пользователь отпускает ранее нажатую клавишу. Ему передаются те же параметры, что и только что рассмотренному методу keyDown. Наш метод keyUp получает контекст отображения, а затем выполняет копирование верхней части окна, сдвигая ее вниз на размер символов текущего шрифта: g.copyArea(0, 1, dimAppWndDimension.width - 1, dimAppWndDimension.height - yHeight - 5, 0, yHeight + 1); После сдвига метод закрашивает область ввода, расположенную в верхней части аплета, желтым цветом, расчищая таким образом место для ввода новой строки: g.setColor(Color.yellow); g.fillRect(1, 1, dimAppWndDimension.width - 2, yHeight + 1); 5 КОМПОНЕНТЫ В ОКНЕ АПЛЕТА Практически каждое приложение Windows, за исключением самых простейших, имеет такие органы управления, как меню, кнопки, поля редактирования текстовой информации, переключатели с независимой и зависимой фиксацией и списки. Кроме того, приложение Windows может создавать диалоговые панели, содержащие перечисленные выше и другие органы управления. В окне аплета вы также можете разместить некоторые из перечисленных выше органов управления, а именно: * кнопки; * переключатели с независимой фиксацией; * переключатели с зависимой фиксацией; * статические текстовые поля; * однострочные и многострочные поля редактирования текста; * списки; * полосы просмотра Самый большой и едва ли приятный сюрприз для вас это то, что при размещении перечисленных органов управления в окне аплета вы не можете задать для них точные координаты и размеры. Размещением занимается система управления внешним видом Layout Manager, которая располагает органы управления по-своему. Вы, однако, можете задавать несколько режимов размещения (последовательное, в ячейках таблицы и так далее), но не координаты или размеры. Это сделано для обеспечения независимости приложений Java от платформ, на которых они выполняются. Органы управления создаются как объекты классов, порожденных от класса Component (рис. 5.1). Поэтому в дальнейшем мы будем называть органы управления компонентами. Рис. 5.1. Взаимосвязь классов органов управления в приложениях Java Класс Button позволяет создавать стандартные кнопки. Если вам нужна нестандартная кнопка (например, графическая кнопка), вы можете создать ее на базе класса Canvas. Для создания переключателей с независимой или зависимой фиксацией предназначен класс CheckBox. С помощью класса Label вы можете создавать в окне аплета текстовые строки, например, надписи для других компонент. Эти строки не редактируются пользователем. Класс List, как нетрудно догадаться из названия, предназначен для создания списков. С помощью класса Scrollbar вы можете создавать полосы просмотра, которые используются, в частности, могострочными полями редактирования текста. Класс TextComponent служит базовым для двух других классов - TextField и TextArea. Первый из них предназначен для создания однострочных редакторов текста, второй - для создания многострочных редкаторов текста. Для того чтобы понять, как компоненты размещаются на поверхности окна аплета системой Layout Manager, рассмотрим другую взаимосвязь классов Java, показанную на рис. 5.2. Рис. 5.2. Компоненты и контейнеры Класс Component служит в качестве базового класса для класса Container. Объекты этого класса, которые мы будем называть контейнерами, могут содержать объекты классов Component и Container. Таким образом, внутри контейнеров могут находиться компоненты и другие контейнеры. Класс Applet, так же как и другие классы, произведенные от класса Container, является контейнером. Это означает, что аплет может содержать в себе компоненты (такие как органы управления) и контейнеры. Заметим, что класс Applet наследуется от класса Container через класс Panel, в котором определены методы системы Layout Manager. Настраивая соответствующим образом Layout Manager, мы можем менять стратегию размещения компонент внутри окна аплета. В окне аплета вы можете создать несколько объектов класса Panel (панелей), разделяющих окно на части. Для каждой такой панели можно установить свою стратегию размещения компонент и контейнеров, что позволяет выполнять достаточно сложную компоновку в окне аплета. Теперь после такого краткого введения в контейнеры и компоненты мы перейдем к описанию методов создания отдельных компонент. Кнопки Как мы уже говорили, стандартные кнопки создаются на базе класса Button. Этот класс очень простой, поэтому мы приведем его определение полностью: public class java.awt.Button extends java.awt.Component { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- public Button(); public Button(String label); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createButton public void addNotify(); // Получение надписи на кнопке public String getLabel(); // Получение строки параметров, отражающей // состоние кнопки protected String paramString(); // Установка надписи на кнопке public void setLabel(String label); } В классе Button определены два конструктора, первый из которых позволяет создавать кнопку без надписи, а второй - кнопку с надписью. Обычно используется именно второй конструктор. Из методов класса Button вы будете использовать чаще всего два - getLabel и setLabel. Первый из них позволяет получить строку надписи на кнопке, а второй - установить новую надпись. Обычно аплет создает в своем окне кнопки в процессе своей инициализации при обработке метода init, например: Button btn1; . . . public void init() { btn1 = new Button("Button 1"); add(btn1); } Здесь мы создали кнопку с надписью Button 1 и добавили ее в контейнер, которым является окно аплета, с помощью метода add. Обработка событий от кнопки Для обработки событий, создаваемых кнопками и другими компонентами, вы можете переопределить метод handleEvent, описанный нами ранее. Однако существует более простой способ. Этот способ основан на переопределении метода action, который получает управление, когда пользователь совершает какое-либо действие с компонентом. Под действием подразумевается нажатие на кнопку, завершение ввода текстовой строки, выбор элемента из списка, изменение состояния переключателя и так далее. Прототип метода action представлен ниже: public boolean action(Event evt, Object obj) { . . . } В качестве первого параметра методу передается ссылка на объект класса Event, содержащий всю информацию о событии. Второй параметр представляет собой ссылку на объект, вызвавший появление события. Как обрабатывать событие в методе action? Прежде всего необходимо проверить, объект какого типа создал событие. Это можно сделать, например, следующим образом: if(evt.target instanceof Button) { . . . return true; } return false; Здесь мы с помощью оператора instanceof проверяем, является ли объект, вызвавший появление события, объектом класса Button. Далее, если в окне аплета имеется несколько кнопок, необходимо выполнить ветвление по ссылкам на объекты кнопок, как это показано ниже: if(evt.target.equals(btn1)) { . . . } else if(evt.target.equals(btn2)) { . . . } . . . else { return false; } return true; Тем из вас, кто создавал приложения Windows на языке программирования С, этот фрагмент кода может напомнить длинный переключатель switch обработки сообщений Windows. Приложение ButtonPress В окне приложения ButtonPress мы создаем четыре кнопки с названиями от Button 1 до Button 4. Когда пользователь нажимает на одну из кнопок, название нажатой кнопки отображается в окне аплета и в строке состояния навигатора (рис. 5.3). Рис. 5.3. Кнопки в окне аплета ButtonPress Обратите внимание на расположение кнопок. По мере добавления, кнопки располагаются по горизонтали справа налево и центрируются в окне аплета. Если бы ширины окна аплета не хватило для размещения четырех кнопок, не поместившиеся кнопки были бы нарисованы ниже. Такую стратегию размещения выбирает по умолчанию система Layout Manager класса Panel, от которого, как вы знаете, произошел класс Applet. Исходные тексты приложения ButtonPress Исходный текст приложения ButtonPress приведен в листинге 5.1. Листинг 5.1. Файл ButtonPress\ButtonPress.java // ========================================================= // Работа с кнопками // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class ButtonPress extends Applet { // Создаем четыре ссылки на объекты типа Button Button btn1; Button btn2; Button btn3; Button btn4; // Строка для записи названия нажатой кнопки String sTextLabel; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающий строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ButtonPress\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем четыре кнопки btn1 = new Button("Button 1"); btn2 = new Button("Button 2"); btn3 = new Button("Button 3"); btn4 = new Button("Button 4"); // Добавляем кнопки в контейнер, которым является // окно аплета add(btn1); add(btn2); add(btn3); add(btn4); // Название кнопки, нажатой в последний раз sTextLabel = new String(""); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Получаем название кнопки sTextLabel = btn.getLabel(); // Выполняем ветвление по кнопкам. Для каждой кнопки // записываем ее название // в строку состояния навигатора if(evt.target.equals(btn1)) { showStatus( "Button 1 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn2)) { showStatus( "Button 2 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn3)) { showStatus( "Button 3 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn4)) { showStatus( "Button 4 (\"" + sTextLabel + "\") pressed"); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Перерисовываем окно аплета repaint(); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем название нажатой кнопки g.drawString("Button (\"" + sTextLabel + "\") pressed", 10, 70); } } Исходный текст документа HTML, созданный для аплета ButtonPress, представлен в листинге 5.2. Листинг 5.2. Файл ButtonPress\ButtonPress.html ButtonPress

The source. Описание исходного текста После того как исходный текст приложения был создан системой Java Applet Wizard, мы добавили поля для хранения ссылок на кнопки и текстовую строку для записи метки нажатой кнопки, а также добавили и изменили несколько методов. Поля класса ButtonPress Четыре поля класса Button с именами btn1, btn2, btn3 и btn4 предназначены для хранения ссылок на кнопки, размещенные в окне нашего аплета: Button btn1; Button btn2; Button btn3; Button btn4; В поле sTextLabel класса String хранится строка, предназначенная для записи названия нажатой кнопки: String sTextLabel; Метод getAppletInfo Метод getAppletInfo, возвращающий строку информации об аплете, не имеет никаких особенностей. Метод init При инициализации аплета метод init создает четыре кнопки, сохраняя ссылки на них в соответствующих полях нашего класса, а также добавляет эти кнопки в окно аплета, вызывая для этого метод add: public void init() { btn1 = new Button("Button 1"); btn2 = new Button("Button 2"); btn3 = new Button("Button 3"); btn4 = new Button("Button 4"); add(btn1); add(btn2); add(btn3); add(btn4); sTextLabel = new String(""); } После добавления кнопок в строку sTextLabel записывается пустое значение, так как ни одна кнопка еще не была нажата. Метод action Метод action проверяет, является ли объект, создавший событие, кнопкой. Для этого он сравнивает ссылку на объект, передаваемую через поле evt.target, с объектом Button, пользуясь оператором instanceof. Так как поле evt.target может содержать ссылку на любой объект, способный создавать события, а не только на объект типа Button, эта проверка необходима для исключения ложных срабатываний на чужие события. Если событие создано кнопкой, ссылка на эту кнопку сохраняется в переменной btn: Button btn; btn = (Button)evt.target; При этом мы выполняем преобразование типов. Далее метод action получает название кнопки (то есть строку, написанную на поверхности кнопки) и сохраняет его в переменной sTextLabel: sTextLabel = btn.getLabel(); Для получения строки названия кнопки используется метод getLabel, определенный в классе Button. Затем метод action проверяет, от какой конкретно кнопки пришло событие, выполняя ветвление с помощью оператора if - else if - else: if(evt.target.equals(btn1)) { showStatus("Button 1 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn2)) { showStatus("Button 2 (\"" + sTextLabel + "\") pressed"); } . . . else { return false; } Название нажатой кнопки отображается в строке состояния навигатора. Если событие создано кнопкой, обработчик которой не предусмотрен в нашем методе action, метод просто возвращает значение false, отказываясь таким образом от обработки события. Если ваша реализация метода action не обрабатывает событие, она может передать его методу action базового класса, как это показано ниже: super.action(evt, obj); В том случае, когда событие было обработано, метод action перерисовывает окно аплета, вызывая метод repaint, и затем возвращает значение true: repaint(); return true; Метод paint Метод paint не содержит никакого кода для рисования кнопок, так как эта задача решается в рамках класса Button. После раскрашивания фона окна аплета и рисования рамки вокруг него, метод paint пишет название нажатой кнопки в окне аплета с помощью метода drawString: g.drawString("Button (\"" + sTextLabel + "\") pressed", 10, 70); Переключатели Аплеты Java могут создавать в своем окне переключатели двух типов: с независимой фиксацией и с зависимой фиксацией. Переключатели с независимой фиксацией имеют прямоугольную форму и, исходя из названия, работают независимо друг от друга. Если такой переключатель находится во включенном состоянии, внутри изображения маленького квадрата появляется галочка, если в выключенном - галочка исчезает. Обычно переключатели с независимой фиксацией используются для управления независящими друг от друга режимами или параметрами. Переключатели с зависимой фиксацией имеют круглую форму. В каждый момент времени может быть включен только один такой переключатель из группы переключателей с фиксацией. Аплет может создавать несколько групп переключателей с зависимой фиксацией. Переключатели с зависимой фиксацией используются для выбора из нескольких взаимоисключающих возможностей, например, для установки одного из нескольких режимов. Создание переключателей с независимой фиксацией Переключатели с независимой и зависимой фиксацией создаются на базе класса Checkbox: public class java.awt.Checkbox extends java.awt.Component { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание переключателя с независимой фиксацией // без названия public Checkbox(); // Создание переключателя с независимой фиксацией // и названием public Checkbox(String label); // Создание переключателя с зависимой фиксацией // и названием public Checkbox(String label, CheckboxGroup group, boolean state); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createCheckbox public void addNotify(); // Получение группы, к которой относится // данный переключатель с зависимой фиксацией public CheckboxGroup getCheckboxGroup(); // Получение названия переключателя public String getLabel(); // Определение текущего состояния переключателя public boolean getState(); // Получение строки параметров protected String paramString(); // Установка группы, к которой относится // данный переключатель с зависимой фиксацией public void setCheckboxGroup(CheckboxGroup g); // Установка названия переключателя public void setLabel(String label); // Установка нового состояния переключателя public void setState(boolean state); } Создать переключатель с независимой фиксацией не сложнее, чем создать кнопку: Checkbox rdbox1; . . . public void init() { chbox1 = new Checkbox("Switch 1"); add(chbox1); } В этом фрагменте кода мы создаем переключатель chbox1 с названием Switch 1, а затем с помощью метода add добавляем его в контейнер, которым является окно аплета. Для определения текущего состояния переключателя вы можете использовать метод getState. Если переключатель включен, этот метод возвращает значение true, а если выключен - значение false. Создание переключателей с зависимой фиксацией Для каждой группы переключателей с зависимой фиксацией вы должны создать объект класса CheckboxGroup: public class java.awt.CheckboxGroup extends java.lang.Object { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public CheckboxGroup(); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Получение ссылки на переключатель, который // находится во включенном состоянии public Checkbox getCurrent(); // Установка указанного переключателя в группе // во включенное состояние public void setCurrent(Checkbox box); // Получение строки, которая представляет группу public String toString(); } Ссылка на этот объект указывается при создании отдельных переключателей с зависимой фиксацией, входящих в группу: CheckboxGroup grModeGroup; Checkbox rdbox1; Checkbox rdbox2; Checkbox rdbox3; Checkbox rdbox4; . . . public void init() { grModeGroup = new CheckboxGroup(); rdbox1 = new Checkbox("Mode 1",grModeGroup, true); rdbox2 = new Checkbox("Mode 2",grModeGroup, false); rdbox3 = new Checkbox("Mode 3",grModeGroup, false); rdbox4 = new Checkbox("Mode 4",grModeGroup, false); add(rdbox1); add(rdbox2); add(rdbox3); add(rdbox4); } Через первый параметр конструктору Checkbox в этом примере передается название переключателя, через второй - ссылка на группу, а через третий - состояние, в которое должен быть установлен переключатель. Из всех переключателей группы только один может находиться во включенном состоянии. Приложение CheckBoxes Для демонстрации методов работы с различными переключателями мы подготовили приложение CheckBoxes. Окно соответствующего аплета показано на рис. 5.4. Рис. 5.4. Окно аплета CheckBoxes с переключателями и кнопкой В верхней части окна располагаются четыре переключателя с зависимой фиксацией, принадлежащих к одной группе. Ниже находятся три переключателя с независимой фиксацией, а еще ниже - кнопка с надписью Get CheckBoxes state. Нажав на эту кнопку, вы можете увидеть в нижней части окна аплета список включенных переключателей. Одновременно может быть включен только один из переключателей Mode 1 - Mode 4, так как эти переключатели составляют группу переключателей с зависимой фиксацией. Переключатели Switch 1, Switch 2 и Switch 3 могут находиться в произвольном состоянии независимо друг от друга. Заметим, что переключатели и кнопка размещались в окне аплета автоматически по мере добавления. Если бы мы добавляли эти компоненты в другой последовательности или если бы окно аплета имело другие размеры, то переключатели могли бы не оказаться сгруппированными, как это показано на рис. 5.4. Позже в этой главе мы научим вас настраивать систему Layout Manager таким образом, чтобы вы смогли располагать компоненты в заданном вами порядке с предсказуемым результатом. Исходные тексты приложения CheckBoxes Файл исходного текста приложения CheckBoxes представлен в листинге 5.3. Листинг 5.3. Файл CheckBoxes\CheckBoxes.java // ========================================================= // Работа с переключателями // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class CheckBoxes extends Applet { // Создаем три ссылки на объекты типа Checkbox Checkbox chbox1; Checkbox chbox2; Checkbox chbox3; // Создаем ссылку на объект типа CheckboxGroup CheckboxGroup grModeGroup; // Создаем четыре ссылки на объекты типа Checkbox Checkbox rdbox1; Checkbox rdbox2; Checkbox rdbox3; Checkbox rdbox4; // Создаем ссылку на объект типа Button Button btnGet; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: CheckBoxes\r\n" + "WWW: http://www.glasnet.ru/~frolov" + "E-mail: [email protected]" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем желтый цвет фона setBackground(Color.yellow); // Создаем три переключателя с независимой фиксацией chbox1 = new Checkbox("Switch 1"); chbox2 = new Checkbox("Switch 2"); chbox3 = new Checkbox("Switch 3"); // Создаем группу переключателей с зависимой фиксацией grModeGroup = new CheckboxGroup(); // Создаем четыре переключателя с зависимой фиксацией, // принадлежащие группе grModeGroup rdbox1 = new Checkbox("Mode 1",grModeGroup, true); rdbox2 = new Checkbox("Mode 2",grModeGroup, false); rdbox3 = new Checkbox("Mode 3",grModeGroup, false); rdbox4 = new Checkbox("Mode 4",grModeGroup, false); // Создаем кнопку, предназначенную для определения // текущего состояния переключателей btnGet = new Button("Get CheckBoxes state"); // Добавляем в окно аплета четыре переключателя // с зависимой фиксацией add(rdbox1); add(rdbox2); add(rdbox3); add(rdbox4); // Добавляем в окно аплета три переключателя // с независимой фиксацией add(chbox1); add(chbox2); add(chbox3); // Добавляем в окно аплета кнопку add(btnGet); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Выполняем ветвление по кнопкам. if(evt.target.equals(btnGet)) { showStatus("Button 1 pressed"); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Перерисовываем окно аплета repaint(); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Строка для записи списка // состояния переключателей String s = new String("> "); // Проверяем переключатели с независимой фиксацией if(chbox1.getState()) s = s + chbox1.getLabel() + ", "; if(chbox2.getState()) s = s + chbox2.getLabel() + ", "; if(chbox3.getState()) s = s + chbox3.getLabel() + ", "; if(rdbox1.getState()) s = s + rdbox1.getLabel(); // Проверяем переключатели с зависимой фиксацией else if(rdbox2.getState()) s = s + rdbox2.getLabel(); else if(rdbox3.getState()) s = s + rdbox3.getLabel(); else if(rdbox4.getState()) s = s + rdbox4.getLabel(); // Рисуем строку состояния переключателей g.drawString(s, 10, 150); } } В листинге 5.4 вы найдете исходный текст документа HTML, который был создан системой Java Applet Wizard для нашего аплета. Листинг 5.4. Файл CheckBoxes\CheckBoxes.html CheckBoxes

The source. Описание исходного текста Рассмотрим поля класса CheckBoxes и переопределенные нами методы. Поля класса CheckBoxes В нашем классе мы определили несколько полей, предназначенных для хранения ссылок на переключатели, группу переключателей и кнопку. Ссылки на переключатели имеют тип Checkbox: Checkbox chbox1; Checkbox chbox2; Checkbox chbox3; Checkbox rdbox1; Checkbox rdbox2; Checkbox rdbox3; Checkbox rdbox4; Для того чтобы сгруппировать переключатели с зависимой фиксацией в группу, мы создали ссылку на объект класса CheckboxGroup: CheckboxGroup grModeGroup; Кроме того, нам потребуется ссылка на объект класса Button: Button btnGet; Метод getAppletInfo Метод getAppletInfo возвращает информацию о нашем аплете. Метод init До сих пор для изменения цвета фона окна наших аплетов мы раскрашивали окно в желтый цвет явным образом в методе paint. Однако есть и другой способ, основанный на вызове метода setBackground: setBackground(Color.yellow); Дополнением к этому методу может послужить метод setForeground, с помощью которого можно установить цвет для рисования в окне. Почему мы выбрали другой способ изменения фона окна? Дело в том, что переключатели сами по себе являются окнами, обладающими такими атрибутами, как цвет фона и цвет изображения. Если просто нарисовать их в окне, закрашенным желтым цветом, то получится, что серые переключатели нарисованы на желтом фоне, что некрасиво. Метод setBackground, вызванный для окна аплета, позволяет задать цвет фона не только для контейнера, но и для всех компонент, расположенных в нем. После установки цвета фона метод init создает три переключателя с независимой фиксацией, указывая их название: chbox1 = new Checkbox("Switch 1"); chbox2 = new Checkbox("Switch 2"); chbox3 = new Checkbox("Switch 3"); Далее метод init создает группу переключателей с зависимой фиксацией в виде объекта класса CheckboxGroup: grModeGroup = new CheckboxGroup(); Для создания переключателей с зависимой фиксацией необходимо использовать метод, допускающий указание группы и начального состояния переключателя: rdbox1 = new Checkbox("Mode 1",grModeGroup, true); rdbox2 = new Checkbox("Mode 2",grModeGroup, false); rdbox3 = new Checkbox("Mode 3",grModeGroup, false); rdbox4 = new Checkbox("Mode 4",grModeGroup, false); Затем метод init создает кнопку с названием Get CheckBoxes state, предназначенную для определения текущего состояния переключателей: btnGet = new Button("Get CheckBoxes state"); После создания компонент они добавляются в контейнер, которым является окно аплета. Для этого используется метод add. Прежде всего мы добавляем четыре переключателя с зависимой фиксацией: add(rdbox1); add(rdbox2); add(rdbox3); add(rdbox4); Размеры окна и размеры переключателей соотносятся между собой таким образом, что в верхней части окна аплета помещаются как раз четыре переключателя. Очевидно, если изменить размеры окна аплета, переключатели будут размещены по-другому. Далее метод init добавляет в окно аплета переключатели с независимой фиксацией и кнопку: add(chbox1); add(chbox2); add(chbox3); add(btnGet); Метод action Метод action обрабатывает только те события, которые вызваны кнопкой btnGet: if(evt.target instanceof Button) { if(evt.target.equals(btnGet)) showStatus("Button 1 pressed"); else return false; repaint(); return true; } Когда пользователь нажимает кнопку, метод action выводит сообщение об этом в строку состояния навигатора и перерисывавает окно аплета. Текущее состояние кнопок определяется методом paint во время перерисовки окна. Метод paint В методе paint мы не закрашиваем желтым цветом окно аплета, так как на этапе инициализации в обработчике метода init был установлен желтый цвет фона окна. Однако черную рамку вокруг границы окна аплета мы все же рисуем. Основная задача метода paint заключается в отображении в нижней части окна аплета списка включенных переключателей. Для формирования этой строки мы создаем объект s класса String: String s = new String("> "); Далее мы проверяем по очереди состояние всех переключателей с независимой фиксацией, дописывая к строке s название включенных переключателей: if(chbox1.getState()) s = s + chbox1.getLabel() + ", "; if(chbox2.getState()) s = s + chbox2.getLabel() + ", "; if(chbox3.getState()) s = s + chbox3.getLabel() + ", "; Для определения текущего состояния переключателей мы вызываем метод getState. Этот метод возвращает значение true для включенного переключателя и false - для выключенного. Название переключателя легко определить с помощью метода getLabel. Проверка состояний переключателей с зависимой фиксацией выполняется аналогично, но с учетом того, что одновременно может быть включен только один такой пеерключатель: if(rdbox1.getState()) s = s + rdbox1.getLabel(); else if(rdbox2.getState()) s = s + rdbox2.getLabel(); else if(rdbox3.getState()) s = s + rdbox3.getLabel(); else if(rdbox4.getState()) s = s + rdbox4.getLabel(); После завершения формирования строки s она отображается в окне аплета методом drawString: g.drawString(s, 10, 150); Списки класса Choice На базе класса Choice вы можете создать списки типа Drop Down или, как их еще называют, “выпадающие” списки. Такой список выглядит как текстовое поле высотой в одну строку, справа от которого располагается кнопка (рис. 5.5). Рис. 5.5. Список типа Drop Down, созданный на базе класса Choice Если нажать на эту кнопку, список раскроется и вы сможете сделать выбор из его элементов (рис. 5.6). Рис. 5.6. Раскрытый список, созданный на базе класса Choice В списке класса Choice одновременно можно выбрать только один элемент. Рассмотрим класс Choice. Определение этого класса выглядит несложно: public class java.awt.Choice extends java.awt.Component { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public Choice(); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление элемента в список public void addItem(String item); // Вызов метода createChoice public void addNotify(); // Определение количества элементов в списке public int countItems(); // Получение строки списка по номеру соответствующего // ему элемента списка public String getItem(int index); // Получение номера текущего выбранного элемента public int getSelectedIndex(); // Получение строки, соответствующей текущему // выбранному элементу списка public String getSelectedItem(); // Получение строки параметров protected String paramString(); // Выбор в списке элемента по заданному номеру public void select(int pos); // Выбор в списке элемента по заданной строке public void select(String str); } Конструктор класса Choice не имеет параметров. Создание списка с его помощью не вызовет у вас никаких затруднений: Choice chBackgroundColor; chBackgroundColor = new Choice(); Для наполнения списка используйте метод addItem. В качестве параметра ему необходимо передать текстовую строку, которая будет связана с добавляемым элементом списка: chBackgroundColor.addItem("Yellow"); Далее список можно добавить в окно аплета как компонент с помощью метода add: add(chBackgroundColor); Заметим, что список можно заполнять до или после добавления в окно аплета. После наполнения списка по умолчанию выделяется элемент, который был добавлен в список первым. При помощи метода select вы можете выделить любой элемент списка по его номеру или строке, связанной с элементом. Когд пользователь выбирает новую строку в списке, возникает событие. Обработчик этого события, реализованный, например, переопределением метода action, может получить номер выбранной строки при помощи метода getSelectedIndex. Пример обработки такого события вы найдете в разделе “Приложение ChoiceList”. Если вас интересует не номер выбранного элемента, а строка, связанная с выбранным элементом, воспользуйтесь методом getSelectedItem. И, наконец, с помощью метода getItem вы можете получить текст строки, связанной с элементом, по номеру элемента. Приложение ChoiceList В прложении ChoiceList мы создали два списка, первый из которых управляет цветом фона окна аплета, а второй - цветом изображения, то есть цветом, которым рисуется изображение в этом окне (рис. 5.7). Рис. 5.7. Окно аплета ChoiceList, в котором создано два списка класса Choice Пользоваться этим аплетом очень просто - выбирайте из левого списка цвет фона, а из правого - цвет изображения, при этом следите за цветом, которым нарисована текстовая строка и рамка вокруг окна аплета. Исходные тексты приложения ChoiceList Исходный текст приложения ChoiceList вы найдете в листинге 5.5. Листинг 5.5. Файл ChoiceList\ChoiceList.java // ========================================================= // Списки типа Drop Down // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class ChoiceList extends Applet { // Создаем ссылки на объекты класса Choice Choice chBackgroundColor; Choice chForegroundColor; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ChoiceList\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем списки для выбора цвета фона и // цвета изображения chBackgroundColor = new Choice(); chForegroundColor = new Choice(); // Добавляем списки в окно аплета add(chBackgroundColor); add(chForegroundColor); // Заполняем список цвета фона chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); // Заполняем список цвета изображения chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); // Устанавливаем цвет фона setBackground(Color.yellow); // Устанавливаем цвет изображения setForeground(Color.black); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Переменная для хранения ссылки на список, // вызвавший событие Choice ch; // Получаем ссылку на список ch = (Choice)evt.target; // Проверяем, что событие вызвано списком, а не // другим компонентом if(evt.target instanceof Choice) { // Выполняем ветвление по спискам // Список цвета фона if(evt.target.equals(chBackgroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет фона if(ch.getSelectedIndex() == 0) setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) setBackground(Color.green); else if(ch.getSelectedIndex() == 2) setBackground(Color.white); } // Список цвета изображения else if(evt.target.equals(chForegroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет изображения if(ch.getSelectedIndex() == 0) setForeground(Color.black); else if(ch.getSelectedIndex() == 1) setForeground(Color.red); else if(ch.getSelectedIndex() == 2) setForeground(Color.blue); } // Если событие возникло от неизвестного списка, // мы его не обрабатываем else { return false; } // Перерисовываем окно аплета repaint(); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем строку g.drawString("Смотри на цвет фона и текста!", 10, 50); } } Исходный текст документа HTML, созданного для нашего аплета, приведен в листинге 5.6. Листинг 5.6. Файл ChoiceList\ChoiceList.html ChoiceList

The source. Описание исходного текста Рассмотрим поля класса ChoiceList и переопределенные нами методы. Поля класса ChoiceList В нашем классе мы определили два поля для хранения ссылок на списки цвета фона и цвета изображения: Choice chBackgroundColor; Choice chForegroundColor; Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете ChoiceList. Метод init В методе init мы создаем два списка как объекты класса Choice: chBackgroundColor = new Choice(); chForegroundColor = new Choice(); Созданные списки пока пустые, но мы можем добавить их в окно аплета, вызвав метод add: add(chBackgroundColor); add(chForegroundColor); Сразу после добавления списков мы их заполняем, вызывая для соответствующих объектов метод addItem: chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); Элементы, добавленные в список первыми, будут выбраны в списке по умолчанию. В нашем случае будет выбран фон желтого цвета и изображение черного цвета. Такие же цвета мы устанавливаем для окна аплета, вызывая методы setBackground и setForeground: setBackground(Color.yellow); setForeground(Color.black); Метод action Метод action обрабатывает событие, вызванное списками, - выбор элемента из списка. Прежде всего, метод action сохраняет ссылку на список, в котором произошел выбор, в переменной ch: Choice ch; ch = (Choice)evt.target; Далее выполняется проверка факта, что событие вызванно именно списком, после чего происходит анализ, в каком именно списке сделан выбор нового элемента: if(evt.target.equals(chBackgroundColor)) { if(ch.getSelectedIndex() == 0) setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) setBackground(Color.green); else if(ch.getSelectedIndex() == 2) setBackground(Color.white); } else if(evt.target.equals(chForegroundColor)) { if(ch.getSelectedIndex() == 0) setForeground(Color.black); else if(ch.getSelectedIndex() == 1) setForeground(Color.red); else if(ch.getSelectedIndex() == 2) setForeground(Color.blue); } else return false; Обратите внимание, что мы вначале выполняем преобразование типа evt.target к классу Choice, а только затем проверяем, действительно ли событие вызвано списком. Правильно ли это? Вообще говоря, неправильно. Так как в поле evt.target могут находиться ссылки на объекты различных классов, в процессе выполнения преобразования типов может произойти исключение. Если предпринимается попытка выполнить преобразование для несовместимых типов. Но так как в нашем аплете события создаются только списками, исключение не возникает. Корректнее было бы вначале проверить ссылку evt.target на принаддлежность к классу Choice с помощью оператора instanceof, а только потом выполнять преобразование типов. Так мы и будем делать в следующих примерах аплетов, обрабатывающих события от различных источников. С помощью метода getSelectedIndex метод action определяет номер выбранного элемента списка, устанавливая соответствующим образом цвет фона или изображения. Метод paint Обработчик метода paint рисует рамку вокруг окна аплета и текстовую строку в средней части этого окна. Dimension dimAppWndDimension = size(); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); g.drawString("Смотри на цвет фона и текста!", 10, 50); При этом мы не указали цвет фона, а также цвет изображения. При этом используются цвета, установленные методом action при выборе соответствующих строк из списков. Списки класса List На базе класса List вы можете сделать список другого типа, который допускает выбор не только одного, но и нескольких элементов. В отличие от списка, созданного на базе класса Choice, список класса List может занимать прямоугольную область, в которой помещаются сразу несколько элементов. Этот список всегда находится в раскрытом состоянии (рис. 5.8). Рис. 5.8. Список класса List, все элементы которого помещаются в окне списка Если размеры окна списка класса List недостаточны для того чтобы вместить в себя все элементы, в правой части окна списка автоматически создается полоса просмотра, с помощью которой можно пролистать весь список (рис. 5.9). Рис. 5.9. Список класса List с полосой просмотра Описание класса List В классе List определено два конструктора и довольно много различных методов. Ниже мы привели краткое описание класса List: public class java.awt.List extends java.awt.Component { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Конструктор без параметров public List(); // Конструктор, позволяющий указать количество // отображаемых строк и флаг одновременного // выбора нескольких элементов public List(int rows, boolean multipleSelections); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление элемента в список public void addItem(String item); // Добавление элемента в список с указанием номера позиции public void addItem(String item, int index); // Вызов метода createList public void addNotify(); // Переключение списка в режим, при котором возможно // выбирать одновременно несколько элементов public boolean allowsMultipleSelections(); // Удаление из списка всех элементов public void clear(); // Определение количества элементов в списке public int countItems(); // Удаление элемента из заданной позиции public void delItem(int position); // Удаление нескольких элементов public void delItems(int start, int end); // Отмена выделения элемента с заданной позицией public void deselect(int index); // Получение строки, связанной с элементом, по // позиции этого элемента public String getItem(int index); // Определение количества элементов, которые // видны в окне списка public int getRows(); // Определение номера выделенного элемента public int getSelectedIndex(); // Определение номеров выделенных элементов public int[] getSelectedIndexes(); // Получение текстовой строки, связанной с // выделенным элементом public String getSelectedItem(); // Получение ссылки на массив строк, связанных // с выделенными элементами public String[] getSelectedItems(); // Определение номера элемента массива, который // был сделан в последний раз выделенным // с помощью метода makeVisible public int getVisibleIndex(); // Проверка, является ли выделенной // строка с заданным номером public boolean isSelected(int index); // Выполняется свертка элементов списка таким // образом, чтобы элемент с заданным номером // стал видимым public void makeVisible(int index); // Минимальные размеры области, необходимые // для отображения списка public Dimension minimumSize(); // Минимальные размеры области, необходимые // для отображения списка с заданным // количеством строк public Dimension minimumSize(int rows); // Получение строки параметров protected String paramString(); // Предпочтительные размеры области, необходимые // для отображения списка public Dimension preferredSize(); // Предпочтительные размеры области, необходимые // для отображения списка с заданным // количеством строк public Dimension preferredSize(int rows); // Извещение об уничтожении узла public void removeNotify(); // Замещение элемента списка с заданным номером public void replaceItem(String newValue, int index); // Выделение элемента с заданным номером public void select(int index); // Установка или сброс режима одновременного // выделения нескольких строк public void setMultipleSelections(boolean v); } Процесс создания списка класса List несложен: List chBackgroundColor; chBackgroundColor = new List(6, false); При создании списка вы передаете конструктору количество одновременно отображаемых строк и флаг разрешения одновременного выбора нескольких строк. Если значение этого флага равно true, пользователь сможет выбирать из списка одновременно несколько строк, а если false - только одну строку. Для наполнения списка вы можете использовать уже знакомый вам метод addItem: chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); Список класса List добавляется к окну аплета методом add: add(chBackgroundColor); Кратко остановимся на нескольких методах класса List. Если вы разрешили пользователю выбирать из списка одновременно несколько элементов, то для получения ссылки на массив выбранных элементов вам пригодятся методы getSelectedItems и getSelectedIndexes: public String[] getSelectedItems(); public int[] getSelectedIndexes(); С помощью метода setMultipleSelections вы можете динамически включать или выключать режим одновременного выбора нескольких элементов. В некоторых случаях вам может пригодиться метод clear, удаляющий все элементы из списка: public void clear(); Методика использования других методов очевидна из краткого описания класса List, приведенного в этом разделе. Обработка событий от списка класса List В отличие от списка класса Choice, для выбора строки (или нескольких строк) из списка класса List, пользователь должен сделать двойной щелчок левой клавишей мыши по выделенному элементу (или элементам, если выделено несколько элементов). При этом событие можно обработать переопределенным методом action, как мы это делали для списка класса Choice. Однако список класса List создает события не только при двойном щелчке, но и при выделении или отмены выделения элементов, сделанном пользователем одинарным щелчком клавиши мыши. Аплет может перехватывать и обрабатывать такие события, переопределив метод handleEvent. Пример такой обработки вы найдете в исходных текстах приложения ListBox. Приложение ListBox В окне приложения ListBox мы создали два списка класса List, первый из которых предназначен для выбора цвета фона, а второй - для выбора цвета изображения. Размер первого списка достаточен для отображения всех добавленных в него элементов. Размер второго списка специально сделан меньше, поэтому справа от него появилась полоса просмотра (рис. 5.10). Рис. 5.10. Окно приложения ListBox Если вы будете выделять различные строки списков одинарным щелчком клавиши мыши, в нижней части окна и строке состояния навигатора (на рисунке не показана) появятся названия выделенных цветов. Таким образом, аплет отслеживает выделение элементов этих двух списков, отображая связанные с ним текстовые строки. В том случае, если вы сделаете двойной щелчок мышью внутри одного из списков, соответствующим образом изменится цвет фона или цвет изображения (текстовых строк и рамки вокруг окна аплета). Исходные тексты приложения Файл исходных текстов приложения ListBox приведен в листинге 5.7. Листинг 5.7. Файл ListBox\ListBox.java // ========================================================= // Списки типа List Box // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class ListBox extends Applet { // Создаем ссылки на объекты класса List List chBackgroundColor; List chForegroundColor; // Строки для хранения названий выбираемого цвета String sSelBackground = new String("Yellow"); String sSelForeground = new String("Black"); // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ListBox\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем списки для выбора цвета фона и // цвета изображения chBackgroundColor = new List(6, false); chForegroundColor = new List(4, false); // Добавляем списки в окно аплета add(chBackgroundColor); add(chForegroundColor); // Заполняем список цвета фона chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); chBackgroundColor.addItem("Black"); chBackgroundColor.addItem("Red"); chBackgroundColor.addItem("Blue"); // Заполняем список цвета изображения chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); chForegroundColor.addItem("Yellow"); chForegroundColor.addItem("Green"); chForegroundColor.addItem("White"); // Устанавливаем цвет фона setBackground(Color.yellow); // Устанавливаем цвет изображения setForeground(Color.black); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Переменная для хранения ссылки на список, // вызвавший событие List ch; // Получаем ссылку на список ch = (List)evt.target; // Проверяем, что событие вызвано списком, а не // другим компонентом if(evt.target instanceof List) { // Выполняем ветвление по спискам // Список цвета фона if(evt.target.equals(chBackgroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет фона if(ch.getSelectedIndex() == 0) setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) setBackground(Color.green); else if(ch.getSelectedIndex() == 2) setBackground(Color.white); else if(ch.getSelectedIndex() == 3) setBackground(Color.black); else if(ch.getSelectedIndex() == 4) setBackground(Color.red); else if(ch.getSelectedIndex() == 5) setBackground(Color.blue); } // Список цвета изображения else if(evt.target.equals(chForegroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет изображения if(ch.getSelectedIndex() == 0) setForeground(Color.black); else if(ch.getSelectedIndex() == 1) setForeground(Color.red); else if(ch.getSelectedIndex() == 2) setForeground(Color.blue); else if(ch.getSelectedIndex() == 3) setForeground(Color.yellow); else if(ch.getSelectedIndex() == 4) setForeground(Color.green); else if(ch.getSelectedIndex() == 5) setForeground(Color.white); } // Если событие возникло от неизвестного списка, // мы его не обрабатываем else { return false; } // Перерисовываем окно аплета repaint(); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // handleEvent // Обработка событий // ------------------------------------------------------- public boolean handleEvent(Event evt) { // Переменная для хранения ссылки на список List ls; // Нас интересуют события, возникающие // только при выделении нового элемена списка if(evt.id == Event.LIST_SELECT) { // Получаем ссылку на список ls = (List)evt.target; // Получаем текущий выделенный цвет фона if(evt.target.equals(chBackgroundColor)) sSelBackground = ls.getSelectedItem(); // Получаем текущий выделенный цвет изображения else if(evt.target.equals(chForegroundColor)) sSelForeground = ls.getSelectedItem(); // Пишем цвет фона и изображения в строке // состояния навигатора showStatus("(" + sSelBackground + ", " + sSelForeground + ")"); // Перерисовываем окно repaint(); // Возвращаем признак того, что мы обработали // событие самостоятельно return true; } // Для тех событий, которые мы не обрабатываем, // вызываем метод handleEvent из базового класа else return super.handleEvent(evt); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем строку g.drawString("Смотри на цвет фона и текста!", 10, 120); // Отображаем текущий выделенный цвет фона // и изображения g.drawString("Background: " + sSelBackground, 10, 160); g.drawString("Foreground: " + sSelForeground, 10, 190); } } Исходный текст документа HTML, в который встроен аплет ListBox, представлен в листинге 5.8. Листинг 5.8. Файл ListBox\ListBox.html ListBox

The source. Описание исходного текста В классе ListBox мы добавили четыре поля и переопределили несколько методов. Поля класса ListBox В нашем классе мы определили два поля для хранения ссылок на списки цвета фона и цвета изображения, а также две строки для хранения названий выбираемых цветов: List chBackgroundColor; List chForegroundColor; String sSelBackground = new String("Yellow"); String sSelForeground = new String("Black"); Содержимое строк sSelBackground и sSelForeground изменяется в процессе выделения пользователем различных строк списков. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете ListBox. Метод init В методе init мы создаем два списка как объекты класса List: chBackgroundColor = new List(6, false); chForegroundColor = new List(4, false); Первый из этих списков способен одновременно отображать шесть строк, поэтому в нем поместились все шесть цветов для фона. Вертикальный размер второго списка меньше. В результате он снабжается полосой просмотра. Оба списка не предназначены для одновременного выбора нескольких элементов, поэтому в качетсве второго параметра мы передаем конструктору List значение false. Созданные списки добавляются в окно аплета методом add: add(chBackgroundColor); add(chForegroundColor); Сразу после добавления списков мы их заполняем, вызывая для соответствующих объектов метод addItem: chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); chBackgroundColor.addItem("Black"); chBackgroundColor.addItem("Red"); chBackgroundColor.addItem("Blue"); chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); chForegroundColor.addItem("Yellow"); chForegroundColor.addItem("Green"); chForegroundColor.addItem("White"); Затем метод выбирает для фона желтый цвет, а для изображения - черный: setBackground(Color.yellow); setForeground(Color.black); Метод action Метод action обрабатывает событие, вызванное списками, - выбор элемента из списка. Прежде всего, метод action сохраняет ссылку на список, в котором произошел выбор, в переменной ch: List ch; ch = (List)evt.target; Далее выполняется проверка факта, что событие вызванно именно списоком класса List, а затем обрабатываются события, созданные списками: if(evt.target.equals(chBackgroundColor)) { if(ch.getSelectedIndex() == 0) setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) setBackground(Color.green); else if(ch.getSelectedIndex() == 2) setBackground(Color.white); else if(ch.getSelectedIndex() == 3) setBackground(Color.black); else if(ch.getSelectedIndex() == 4) setBackground(Color.red); else if(ch.getSelectedIndex() == 5) setBackground(Color.blue); } else if(evt.target.equals(chForegroundColor)) { if(ch.getSelectedIndex() == 0) setForeground(Color.black); else if(ch.getSelectedIndex() == 1) setForeground(Color.red); else if(ch.getSelectedIndex() == 2) setForeground(Color.blue); else if(ch.getSelectedIndex() == 3) setForeground(Color.yellow); else if(ch.getSelectedIndex() == 4) setForeground(Color.green); else if(ch.getSelectedIndex() == 5) setForeground(Color.white); } else return false; С помощью метода getSelectedIndex метод action определяет номер выбранного элемента списка, устанавливая соответствующим образом цвет фона или изображения. Затем метод перерисовывает окно аплета, вызывая метод repaint. Метод handleEvent Для того чтобы отследить выделение элементов списка, наш аплет переопределил метод handleEvent, обеспечив обработку события с идентификатором Event. LIST_SELECT. Переопределение метода handleEvent нужно делать внимательно, так как этот метод вызывается при возникновении разных событий, например, при перемещении мыши в окне аплета. Если ваш метод handleEvent не обрабатывает какое-либо событие, он должен передать его одноименному методу из базового класса. Наш метод handleEvent прежде всего проверяет код события, обрабатывая только события Event.LIST_SELECT, которые создаются при выделении пользователем элементов списка: if(evt.id == Event.LIST_SELECT) { . . . } else return super.handleEvent(evt); Если событие подлежит обработке, наш метод handleEvent получает ссылку на объект, вызвавший событие, и сохраняет ее в переменной ls типа List: List ls; ls = (List)evt.target; Затем метод определяет, какой сисок создал событие, проверяя поле evt.target, а затем получает и записывает выделенную строку в переменную sSelBackground (для списка цветов фона) или sSelForeground (для списка цветов изображения): if(evt.target.equals(chBackgroundColor)) sSelBackground = ls.getSelectedItem(); else if(evt.target.equals(chForegroundColor)) sSelForeground = ls.getSelectedItem(); После этого цвет фона и изображения записывается в строку состояния навигатора в формате (<цвет фона>, <цвет изображения>): showStatus("(" + sSelBackground + ", " + sSelForeground + ")"); После этого метод выполняет перерисовку окна и возвращает значение true - признак того, что он обработал событие: repaint(); return true; Метод paint Обработчик метода paint рисует рамку вокруг окна аплета и текстовую строку в средней части этого окна. В нижней части окна аплета метод paint отображает выделенные в списках цвета фона и изображения: g.drawString("Background: " + sSelBackground, 10, 160); g.drawString("Foreground: " + sSelForeground, 10, 190); Текстовое поле класса Label На базе класса Label вы можете создать в окне аплета однострочное текстовое поле, которое не поддается редактированию. Основное назначение таких полей - подпись других компонент, таких, например, как группы переключателей или списки. Ниже мы привели краткое описание класса Label: public class java.awt.Label extends java.awt.Component { // ----------------------------------------------------- // Поля // ----------------------------------------------------- // Способ выравнивания текстового поля public final static int CENTER; // центрирование public final static int LEFT; // по левой границе public final static int RIGHT; // по правой границе // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание текстового поля без текста public Label(); // Создание текстового поля с заданным текстом public Label(String label); // Создание текстового поля с заданным текстом // и заданным выравниванием public Label(String label, int alignment); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createLabel public void addNotify(); // Определение текущего выравнивания текстового поля public int getAlignment(); // Получение текста из поля public String getText(); // Получение строки параметров protected String paramString(); // Установка выравнивания текстового поля public void setAlignment(int alignment); // Запись текста в поле public void setText(String label); } Текстовое поле класса Label создается вызовом соответствующего конструктора. Например, ниже мы создали текстовое поле, указав строку, которую надо в него записать: Label lbTextLabel; lbTextLabel = new Label("Выберите выравнивание"); С помощью метода add вы можете добавить текстовое поле в окно аплета: add(lbTextLabel); Метод setAlignment позволяет при необходимости изменить выравнивание текста. Способ выравнивания необходимо указать через единственный параметр метода: lbTextLabel.setAlignment(Label.LEFT); При помощи метода setText вы сможете динамически изменять текст, расположенный в поле класса Label. Приложение TextLabel В окне приложения TextLabel, демонстрирующего способы работы с полями класса Label, мы разместили одно такое поле и три кнопки, позволяющие изменять выравнивание текста в поле (рис. 5.11). Рис. 5.11. Окно приложения TextLabel Нажимая кнопки, вы можете заметить, как строка, расположенная под ними, немного сдвигается по горизонтали. Исходные тексты приложения Исходный текст приложения TextLabel приведен в листинге 5.9. Листинг 5.9. Файл TextLabel\TextLabel.java // ========================================================= // Работа с компонентами Label // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class TextLabel extends Applet { // Создаем ссылку на объекты типа Label Label lbTextLabel; // Создаем три ссылки на объекты типа Button Button btnLeft; Button btnCenter; Button btnRight; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: TextLabel\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем компоненту Label lbTextLabel = new Label("Выберите выравнивание"); // Создаем три кнопки btnLeft = new Button("Влево"); btnCenter = new Button("Центровка"); btnRight = new Button("Вправо"); // Добавляем три кнопки add(btnLeft); add(btnCenter); add(btnRight); // Добавляем компоненту Label add(lbTextLabel); // Устанавливаем цвет фона setBackground(Color.yellow); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Выполняем ветвление по кнопкам if(evt.target.equals(btnLeft)) { // Выравниваем текст компоненты Label // по левой границе lbTextLabel.setAlignment(Label.LEFT); } else if(evt.target.equals(btnCenter)) { // Центруем текст компоненты Label lbTextLabel.setAlignment(Label.CENTER); } else if(evt.target.equals(btnRight)) { // Выравниваем текст компоненты Label // по правой границе lbTextLabel.setAlignment(Label.RIGHT); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } } Исходный текст документа HTML, созданного для размещения аплета, представлен в листинге 5.10. Листинг 5.10. Файл TextLabel\TextLabel.html TextLabel

The source. Описание исходного текста В классе TextLabel мы определили четыре поля и несколько методов. Поля класса TextLabel Мы определили четыре поля - lbTextLabel, btnLeft, btnCenter и btnRight: Label lbTextLabel; Button btnLeft; Button btnCenter; Button btnRight; Первое из них предназначено для хранения ссылки на объект класса Label (однострочное текстовое поле), остальные три - для хранения ссылок на кнопки, определяющие выравнивание. Метод getAppletInfo Метод getAppletInfo возвращает информацию о нашем аплете. Метод init Метод init создает одно текстовое поле, вызывая конструктор с одним параметром - текстовой строкой: lbTextLabel = new Label("Выберите выравнивание"); Далее этот метод создает три кнопки, с помощью которых вы будете изменять выравнивание текста в поле класса Label: btnLeft = new Button("Влево"); btnCenter = new Button("Центровка"); btnRight = new Button("Вправо"); Затем созданные кнопки и поле добавляются в окно аплета при помощи метода add: add(btnLeft); add(btnCenter); add(btnRight); add(lbTextLabel); Последнее, что делает метод init перед возвращением управления, это изменение цвета фона: setBackground(Color.yellow); Метод action Наш метод action обрабатывает только те события, которые вызваны кнопками. Проверка источника события выполняется так же, как и раньше, поэтому мы не будем на этом останавливаться. Что же касается установки выравнивания, то она выполняется при помощи метода setAlignment: if(evt.target.equals(btnLeft)) lbTextLabel.setAlignment(Label.LEFT); else if(evt.target.equals(btnCenter)) lbTextLabel.setAlignment(Label.CENTER); else if(evt.target.equals(btnRight)) lbTextLabel.setAlignment(Label.RIGHT); else return false; Метод paint Единственное, что делает метод paint, - это рисование рамки черного цвета вокруг окна аплета. Текстовое поле класса TextField Для редактирования одной строки текста вы можете создать текстовое поле на базе класса TextField, которое несложно в использовании. Класс TextField создан на базе другого класса с именем TextComponent, поэтому при работе с текстовым полем класса TextField вы можете использовать и методы класса TextComponent. Приведем краткое описание класса TextField: public class java.awt.TextField extends java.awt.TextComponent { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание поля без текста public TextField(); // Создание поля без текста с заданной шириной public TextField(int cols); // Создание поля и инициализация его текстом public TextField(String text); // Создание поля заданной ширины // и инициализация его текстом public TextField(String text, int cols); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createTextField public void addNotify(); // Проверка, установлен ли для поля эхо-символ public boolean echoCharIsSet(); // Определение размера поля public int getColumns(); // Получение текущего эхо-символа public char getEchoChar(); // Определение минимальных размеров области // для отображения поля public Dimension minimumSize(); // Определение минимальных размеров области // для отображения поля заданной ширины public Dimension minimumSize(int cols); // Получение строки параметров protected String paramString(); // Определение оптимальных размеров области // для отображения поля public Dimension preferredSize(); // Определение оптимальных размеров области // для отображения поля заданной ширины public Dimension preferredSize(int cols); // Установка эхо-символа для отображения в поле public void setEchoCharacter(char c); } При создании текстового поля вы можете выбрать один из четырех конструкторов, соответственно, для создания поля без текста и без указания размера, без текста заданного размера, для создания поля с текстом и для создания поля с текстом указанного размера. Вот фрагмент кода, в котором создается поле с текстом, имеющее ширину, достаточную для размещения 35 символов: TextField txt; txt = new TextField("Введите строку текста", 35); Созданное поле добавляется в окно аплета методом add. Большинство самых полезнных методов, необходимых для работы с полем класса TextField, определено в классе TextComponent, краткое описание которого мы привели ниже: public class java.awt.TextComponent extends java.awt.Component { // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Получение текста, выделенного пользователем // в окне поля public String getSelectedText(); // Получение позиции конца выделенной области public int getSelectionEnd(); // Получение позиции начала выделенной области public int getSelectionStart(); // Получение полного текста из поля public String getText(); // Проверка, возможно ли редактирование // текста в поле public boolean isEditable(); // Получение строки параметров protected String paramString(); // Удаление извещения public void removeNotify(); // Выделение заданной области текста public void select(int selStart, int selEnd); // Выделение всего текста public void selectAll(); // Включение или выключение возможности // редактирования текста public void setEditable(boolean t); // Установка текста в поле public void setText(String t); } С помощью метода getText вы можете получить весь текст, который имеется в поле. Метод getSelectedText позволяет получить только ту часть текста, которая предварительно была выделена пользователем. Приложение может выделить любой фрагмент текста или весь текст при помощи методов select и selectAll, соответственно. Для записи текста в поле приложение может воспользоваться методом setText. Возможно, для вас будет интересен метод setEditable, позволяющий переключать текстовое поля из режима, при котором редактирование заблокировано, в режим с разрешенным редактированием и обратно. Приложение TxtField В приложении TxtField мы создали однострочное поле редактирования на базе класса TextField и кнопку, с помощью которой можно извлечь текст из поля для отображения (рис. 5.12). Рис. 5.12. Окно аплета TxtField Изменив текст в поле редактирования, нажмите кнопку “Получить строку”. В нижней части окна аплета вы увидите измененный вами текст. Исходные тексты приложения Исходные тексты приложения TxtField представлены в листинге 5.11. Листинг 5.11. Файл TxtField\TxtField.java // ========================================================= // Однострочное текстовое поле класса TextField // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class TxtField extends Applet { // Создаем ссылку на объекты типа TextField TextField txt; // Создаем ссылку на объекты типа Button Button btnGetText; // Строка для хранения введенных данных String str; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: TxtField\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем редактируемое однострочное текстовое поле txt = new TextField("Введите строку текста", 35); // Создаем кнопку, с помощью которой можно получить // содержимое текстового поля btnGetText = new Button("Получить строку"); // Добавляем текстовое поле в окно аплете add(txt); // Добавляем кнопку в окно аплете add(btnGetText); // Получаем и сохраняем текущий текст, // установленный в поле str = txt.getText(); // Устанавливаем цвет фона setBackground(Color.yellow); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Проверяем ссылку на кнопку if(evt.target.equals(btnGetText)) { // Получаем и сохраняем текущий текст, // установленный в поле str = txt.getText(); // Перерисовываем окно аплета repaint(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем строку, полученную из текстового поля g.drawString("> " + str, 10, 100); } } Документ HTML, созданный для нашего аплета, представлен в листинге 5.12. Листинг 5.12. Файл TxtField\TxtField.html TxtField

The source. Описание исходного текста В классе TxtField мы определили три поля и несколько методов. Поля класса TxtField В поле txt хранится ссылка на объект класса TextField - наше однострочное поле редактирования: TextField txt; В полях btnGetText и str хрянятся, соответственно, ссылки на кнопку и текстовую строку, в которую записывается текущее содержимое поля редактирования: Button btnGetText; String str; Метод getAppletInfo Метод getAppletInfo возвращает информацию о нашем аплете. Метод init Метод init создает одно текстовое поле редактирования, вызывая конструктор с параметром в виде текстовой строки: txt = new TextField("Введите строку текста", 35); Далее этот метод создает кнопку, с помощью которой можно получить текущее содержимое поля редактирования: btnGetText = new Button("Получить строку"); Затем созданные поле и кнопка добавляются в окно аплета при помощи метода add: add(txt); add(btnGetText); После этого метод init получает текущее содержимое поля редактирования и записывает его в строку str: str = txt.getText(); В завершении метод init изменяет цвет фона: setBackground(Color.yellow); Метод action Наш метод action обрабатывает только те события, которые вызваны кнопкой. Обработка заключается в извлечении текста из поля редактирования и записи его в строку str: str = txt.getText(); repaint(); Метод paint После рисования рамки черного цвета вокруг окна аплета метод paint отобаржает текущее содержимое строки str в нижней части окна: g.drawString("> " + str, 10, 100); Многострочное текстовое поле класса TextArea Если вам нужно поле для ввода многострочной информации, обратите внимание на класс TextArea. С его помощью вы можете создать многострочное поле заданной ширины и высоты, снабженное полосами просмотра. Класс TextArea создан на базе класса TextComponent, рассмотренном нами ранее, поэтому для работы с многострочными полями вы можете использовать методы этого класса. В частности, вам доступен метод, с помощью которого можно получать из онка редактирования не весь текст, а только выделенную пользователем область. Краткое описание класса TextArea мы привели ниже: public class java.awt.TextArea extends java.awt.TextComponent { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание поля без текста и без указания размеров public TextArea(); // Создание поля без текста с указанием размеров public TextArea(int rows, int cols); // Создание поля с текстом без указания размеров public TextArea(String text); // Создание поля с текстом и с указанием размеров public TextArea(String text, int rows, int cols); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createTextArea public void addNotify(); // Добавление текста в поле редактирования public void appendText(String str); // Определение количества столбцов поля public int getColumns(); // Определение количества строк поля public int getRows(); // Добавление текста в поле редактирования // начиная с заданной позиции public void insertText(String str, int pos); // Определение минимальных размеров области // для размещения многострочного текстового поля public Dimension minimumSize(); // Определение минимальных размеров области // для размещения многострочного текстового поля // с заданным количеством строк и столбцов public Dimension minimumSize(int rows, int cols); // Получение строки параметров protected String paramString(); // Определение предпочтительных размеров области // для размещения многострочного текстового поля public Dimension preferredSize(); // Определение предпочтительных размеров области // для размещения многострочного текстового поля // с заданным количеством строк и столбцов public Dimension preferredSize(int rows, int cols); // Замещение блока текста, начиная с первой позиции // и до второй позиции public void replaceText(String str, int start, int end); } Когда вы создаете многострочное текстовое поле редактирования, то можете использовать конструктор, допускающий указание размеров поля в строках и столбцах: TextArea txt; txt = new TextArea("Введите строку текста", 5, 35); Созданное поле добавляется в окно аплета методом add. Отметим, что в классе TextArea есть методы для работы с блоками текста (вставка и замена), а также методы, с помощью которых можно определить количество строк и столбцов в поле редактирования. Приложение TextEdit Приложение TextEdit (рис. 5.13) демонстрирует некоторые приемы работы с многострочным полем редактирования текста, созданным на базе класса TextArea. Рис. 5.13. Окно приложения TextEdit В окне редактирования вы можете вводить строки текста. Если нажать на кнопку “Получить все”, в нижней части окна отобразится полное содержимое окна редактирования. Каждая строка будет отделена символом перехода на новую строку. Если же нажать кнопку “Получить выделенное”, в нижней части появится только выделенный фрагмент текста (как это показано на рис. 5.13). Исходные тексты приложения Исходный текст приложения приведен в листинге 5.13. Листинг 5.13. Файл TextEdit\TextEdit.java // ========================================================= // Многострочное текстовое поле класса TextArea // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class TextEdit extends Applet { // Создаем ссылку на объект типа TextArea TextArea txt; // Создаем ссылку на объекты типа Button Button btnGetText; Button btnGetSelectedText; String str; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: TextEdit\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем редактируемое многострочное текстовое поле txt = new TextArea("Введите строку текста", 5, 35); // Создаем кнопку, с помощью которой можно получить // полное содержимое текстового поля btnGetText = new Button("Получить все"); // Создаем кнопку, с помощью которой можно получить // содержимое выделенной области текстового поля btnGetSelectedText = new Button("Получить выделенное"); // Добавляем текстовое поле в окно аплете add(txt); // Добавляем кнопки в окно аплете add(btnGetText); add(btnGetSelectedText); // Получаем и сохраняем текущий текст, // установленный в поле str = txt.getText(); // Устанавливаем цвет фона setBackground(Color.yellow); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Проверяем ссылку на кнопку if(evt.target.equals(btnGetText)) { // Получаем и сохраняем текущий текст, // установленный в поле str = txt.getText(); // Перерисовываем окно аплета repaint(); } else if(evt.target.equals(btnGetSelectedText)) { // Получаем и сохраняем выделенную область str = txt.getSelectedText(); // Перерисовываем окно аплета repaint(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем строку, полученную из текстового поля g.drawString("> " + str, 10, 150); } } Исходный текст документа HTML, созданного для аплета, приведен в листинге 5. 14. Листинг 5.14. Файл TextEdit\TextEdit.html TextEdit

The source. Описание исходного текста В классе TextEdit мы определили четыре поля и несколько методов. Поля класса TxtField В поле txt хранится ссылка на объект класса TextArea - многострочное поле редактирования: TextArea txt; В полях btnGetText, btnGetSelectedText и str хрянятся, соответственно, ссылки на кнопки и текстовую строку, в которую записывается текущее содержимое поля редактирования: Button btnGetText; Button btnGetSelectedText; String str; Метод getAppletInfo Метод getAppletInfo возвращает информацию о нашем аплете. Метод init Метод init создает одно текстовое поле редактирования, вызывая конструктор следующего вида: txt = new TextArea("Введите строку текста", 5, 35); Здесь создается поле из 5 строк и 35 столбцов. Далее этот метод создает кнопки, с помощью которых можно получить текущее содержимое всего поля редактирования и области, выделенной пользователем: btnGetText = new Button("Получить все"); btnGetSelectedText = new Button("Получить выделенное"); Затем созданные поле и кнопки добавляются в окно аплета при помощи метода add: add(txt); add(btnGetText); add(btnGetSelectedText); После этого метод init получает текущее содержимое поля редактирования и записывает его в строку str, а затем изменяет цвет фона: str = txt.getText(); setBackground(Color.yellow); Метод action Наш метод action обрабатывает только те события, которые вызваны кнопками. Обработка заключается в извлечении текста из поля редактирования и записи его в строку str. В зависимости от того, какая кнопка была нажата, извлекается либо весь текст, либо только выделенный фрагмент: if(evt.target.equals(btnGetText)) { str = txt.getText(); repaint(); } else if(evt.target.equals(btnGetSelectedText)) { str = txt.getSelectedText(); repaint(); } else return false; Для извлечения всего текста мы вызываем метод getText, а для извлечения выделенного фрагмента - метод getSelectedText. После записи извлеченного текста метод action перерисовывает окно аплета, вызывая метод repaint. Метод paint После рисования рамки черного цвета вокруг окна аплета метод paint отобаржает текущее содержимое строки str в нижней части окна: g.drawString("> " + str, 10, 100); 6 НАСТРОЙКА СИСТЕМЫ LAYOUT MANAGER В предыдущей главе мы рассказали вам о том, как создавать компоненты и размещать их в контейнере. Однако предложенный способ размещения компонент в окне контейнера едва ли можно назвать удобным, так как заранее трудно предугадать, на каком месте окажется тот или иной орган управления. К счастью, имеются способы, позволяющие контролировать размещение отдельных компонент в окне контейнера. И хотя эти способы не позволяют задавать конкретные координаты и размеры органов управления, использовнные схемы размещения компонент будут правильно работать на любой аппаратной платформе (не забывайте, что Java создавалась как средство разработки приложений, способных выполняться на любой платформе). В чем трудность создания пользовательского интерфейса для мультиплатформных систем? В том, что разработчик приложения никогда не знает характеристики устройства отображения, установленные у пользователя. Он, в частности, не может заранее знать разрешение монитора, размер системного шрифта и другие характеристики, необходимые для компоновки диалоговых панелей в терминах абсолютных координат. Средства пользовательского интерфейса AWT способны динамически измнять размеры компонент, подгоняя их “по месту” в системе пользователя. В результате значительно повышается вероятность того что внешний вид диалоговой панели, в каком она предстанет перед пользователем, будет похож на то, что ожидал разработчик. Как мы уже говорили в начале предыдущей главы, расположением компонент внутри окна контейнера (например, внутри окна аплета) управляет система Layout Manager. Способ, которым она это делает, весьма непривычен для тех, кто создавал приложения Windows. Выбор этого способа обоснован необходимостью обеспечения совместимости с различными компьютерными платформами. Режимы системы Layout Manager Прежде чем мы рассмотрим различные режимы компоновки системы Layout Manager, вспомним, как происходит наследование класса Applet (рис. 6.1). Рис. 6.1. Наследование класса Applet Класс Applet наследуется от класса Panel, который, в свою очередь, наследуется от класса Container и Component. Класс Container пользуется интерфейсом LayoutManager, что позволяет выбирать для контейнеров один из нескольких режимов размещения компонент в окне контейнера. Что же касается класса Panel, то для него по умолчанию выбирается режим размещения компонент с названием Flow Layout. Разумеется, вы можете выбрать другой режим размещения, указав его явным образом. Ниже мы перечислили все возможные режимы системы Layout Manager: Режим размещения компонентОписаниеFlowLayoutКомпоненты заполняют окно контейнера “потоком” по мере их добавления методом add. Они размещаются слева направо и сверху внизGridLayoutКомпоненты размещаются в виде таблицы по мере добавления слева направо и сверху вниз. Для этой таблицы можно указать количество столбцов и строк GridBagLayoutАналогично предыдущему, однако при добавлении компонент в таблицу можно указать координаты ячейки, в которую помещается компонентаBorderLayoutПри размещении компоненты указывается одно из нескольких направлений: юг, север, запад, восток, центр. Направление определяется относительно центра окна контейнераCardLayoutРазмещение компонент друг над другом в одном окне. Этот режим позволяет организовать набор диалоговых панелей в виде блокнота Каждому режиму соответсвует одноименный класс, методы и конструкторы которого позволяют выбирать различные компоновки. Далее на примере конкретных приложений мы рассмотрим использование перечисленных выше режимов системы Layout Manager. Режим FlowLayout В этом режиме мы добавляли компоненты во всех примерах аплетов, приведенных в предыдущей главе, так как по умолчанию для аплетов используется именно режим FlowLayout. Ниже мы привели краткое описание класса FlowLayout: public class java.awt.FlowLayout extends java.lang.Object implements java.awt.LayoutManager { // ----------------------------------------------------- // Поля // ----------------------------------------------------- // Способы выравнивания public final static int CENTER; // центрирование public final static int LEFT; // по левой границе public final static int RIGHT; // по правой границе // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Без указания выравнивания и зазора между компонентами public FlowLayout(); // С указанием выравнивания public FlowLayout(int align); // С указанием выравнивания и зазора между компонентами // по вертикали и горизонтали public FlowLayout(int align, int hgap, int vgap); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Не используется public void addLayoutComponent(String name, Component comp); // Предназначен для того чтобы компоненты могли // установить для себя предпочтительный размер public void layoutContainer(Container target); // Определение минимального размера окна контейнера, // необходимого для размещения всех компонент public Dimension minimumLayoutSize(Container target); // Определение предпочтительного размера окна контейнера, // необходимого для размещения всех компонент public Dimension preferredLayoutSize(Container target); // Удаление компоненты из контейнера public void removeLayoutComponent(Component comp); // Получение строки названия метода компоновки public String toString(); } Обычно приложения не вызывают методы класса FlowLayout, устанавливая варианты компоновки при помощи конструкторов. Первый конструктор класса FlowLayout, не имеющий параметров, устанавливает по умолчанию режим центрирования компонент и зазор между компонентами по вертикали и горизонтали, равный 5 пикселам. Именно этот режим и использовался во всех наших аплетах из предыдущей главы, так как именно он применяется по умолчанию объектами класса Panel, от которого наследуется класс Applet. С помощью второго конструктора вы можете выбрать режим размещения с заданным выравниванием компонент в окне контейнера по горизонтали. В качестве параметров этому конструктору необходимо передавать значения FlowLayout.LEFT, FlowLayout. RIGHT, или FlowLayout.CENTER. Зазор между компонентами будет при этом равен по умолчанию 5 пикселам. И, наконец, третий конструктор допускает раздельное указание режима выравнивания, а также зазоров между компонентами по вертикали и горизонтали в пикселах. Режим GridLayout В режиме GridLayout компоненты размещаются в ячейках таблицы, параметры которой можно задать с помощью конструкторов класса GridLayout: public class java.awt.GridLayout extends java.lang.Object implements java.awt.LayoutManager { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание таблицы с заданным // количеством строк и столбцов public GridLayout(int rows, int cols); // Создание таблицы с заданным количеством строк и // столбцов и с заданным зазором между компонентами public GridLayout(int rows, int cols, int hgap, int vgap); // ----------------------------------------------------- // Методы // ----------------------------------------------------- public void addLayoutComponent(String name, Component comp); public void layoutContainer(Container target); public Dimension minimumLayoutSize(Container target); public Dimension preferredLayoutSize(Container target); public void removeLayoutComponent(Component comp); public String toString(); } При размещении компонент внутри ячеек таблицы все они получают одинаковые размеры. Если один из параметров, задающих размерность таблицы, равен нулю, это означает, что соответствующий столбец или строка может содержать любое количество элементов. Заметим, что оба параметра rows и cols не могут быть равны нулю одновременно. Приложение Grid Приложение Grid демонстрирует использование режима размещения компонент GridLayout. В окне аплета мы создали таблицу размером 3х3, разместив в ее ячейках восемь кнопок (рис. 6.2). Рис. 6.2. Окно приложения Grid Обратите внимание, что все кнопки в окне аплета имеют одинаковый размер. Если вы станете нажимать на них, в строке состояния навигатора будет появляться название нажатой кнопки (на рис. 6.2 не показано). Исходные тексты приложения Исходный текст аплета Grid приведен в листинге 6.1. Листинг 6.1. Файл Grid\Grid.java // ========================================================= // Режим компоновки GridLayout // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Grid extends Applet { // Создаем ссылки на объекты типа Button Button btn1; Button btn2; Button btn3; Button btn4; Button btn5; Button btn6; Button btn7; Button btn8; // Строка для записи названия нажатой кнопки String sTextLabel; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Grid\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.1"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем желтый цвет фона setBackground(Color.yellow); // Создаем кнопки btn1 = new Button("Button 1"); btn2 = new Button("Button 2"); btn3 = new Button("Button 3"); btn4 = new Button("Button 4"); btn5 = new Button("Button 5"); btn6 = new Button("Button 6"); btn7 = new Button("Button 7"); btn8 = new Button("Button 8"); // Устанавливаем режим GridLayout setLayout(new GridLayout(3, 3)); // Добавляем кнопки в контейнер add(btn1); add(btn2); add(btn3); add(btn4); add(btn5); add(btn6); add(btn7); add(btn8); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Получаем название кнопки sTextLabel = btn.getLabel(); // Записываем название кнопки // в строку состояния навигатора showStatus("Button (\"" + sTextLabel + "\") pressed"); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } } Листинг 6.2 содержит исходный текст документа HTML, в который встроен аплет. Листинг 6.2. Файл Grid\Grid.html Grid

The source. Описание исходного текста В исходном тексте приложения Grid вы сможете разобраться саомстоятельно. Заметим только, что в методе init мы вызываем метод setLayout, передавая ему объект класса GridLayout: setLayout(new GridLayout(3, 3)); В свою очередь, параметры конструктора объекта GridLayout определяют, что для размещения компонент будет использована таблица, состоящая из трех строк и трех столбцов. Компоненты (кнопки) добавляются в окно аплета хорошо известным вам способом - с помощью метода add: add(btn1); add(btn2); add(btn3); add(btn4); add(btn5); add(btn6); add(btn7); add(btn8); Как видно из рис. 6.2, таблица заполняется кнопками слева направо и сверху вниз, как это и предполагает режим компоновки GridLayout. Режим BorderLayout При использовании режима BorderLayout окно контейнера разделяется на рамку и центральную часть. При размещении компонент указывается направление от центра окна, в котором слудует размещать компоненты. Ниже приведено краткое описание класса BorderLayout: public class java.awt.BorderLayout extends java.lang.Object implements java.awt.LayoutManager { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public BorderLayout(); public BorderLayout(int hgap, int vgap); // ----------------------------------------------------- // Методы // ----------------------------------------------------- public void addLayoutComponent(String name, Component comp); public void layoutContainer(Container target); public Dimension minimumLayoutSize(Container target); public Dimension preferredLayoutSize(Container target); public void removeLayoutComponent(Component comp); public String toString(); } Два конструктора предназначены для создания схемы размещения, соответственно, без зазора между компонентами и с зазором заданной величины. Добавляя компоненты к контейнеру, вы должны использовать метод add с двумя параметрами, первый из которых указывает направление размещения, а второй - ссылку на добавляемый объект: add("North", btn1); add("East", btn2); add("West", btn3); add("South", btn4); add("Center", btn5); Приложение Border В приложении Border создается пять кнопок, которые размещаются в режиме BorderLayout (рис. 6.3). Рис. 6.3. Окно аплета Border Заметьте, что в процессе размещения северная и южная кнопка (верхняя и нижняя на нашем рисунке) заняли по ширине все окно аплета. Высота же этих кнопок была установлена достаточной для размещения одной строки текста. Восточная и западная кнопка (левая и правая) имеют ширину, достаточную для размещения текста, и высоту, равную высоте области, которая осталась от сервеной и южной кнопок. Для центральной кнопки было выделено все оставшееся пространство в центре окна аплета. Исходные тексты приложения Исходный текст приложения Border приведен в листинге 6.3. Листинг 6.3. Файл Border\Border.java // ========================================================= // Режим компоновки BorderLayout // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Border extends Applet { // Создаем ссылки на объекты типа Button Button btn1; Button btn2; Button btn3; Button btn4; Button btn5; // Строка для записи названия нажатой кнопки String sTextLabel; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Grid\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.1"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем желтый цвет фона setBackground(Color.yellow); // Создаем кнопки btn1 = new Button("Button North"); btn2 = new Button("Button East"); btn3 = new Button("Button West"); btn4 = new Button("Button South"); btn5 = new Button("Button Center"); // Устанавливаем режим GridLayout setLayout(new BorderLayout()); // Добавляем кнопки в контейнер add("North", btn1); add("East", btn2); add("West", btn3); add("South", btn4); add("Center", btn5); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Получаем название кнопки sTextLabel = btn.getLabel(); // Записываем название кнопки // в строку состояния навигатора showStatus("Button (\"" + sTextLabel + "\") pressed"); // возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } } Документ HTML, который был создан для нашего аплета, вы найдете в листинге 6. 4. Листинг 6.4. Файл Border\Border.html Border

The source. Описание исходного текста Все самое интересное в приложении Border происходит в методе init, поэтому мы ограничимся описанием только этого метода. Прежде всего метод init устанавливает желтый цвет фона: setBackground(Color.yellow); И хотя весь фон в нашем аплете закрыт кнопками, вы можете изменить это, выбрав конструктор класса BorderLayout, допускающий задание зазора между компонентами по вертикали и горизонтали. Далее метод init создает пять кнопок для размещения в окне аплета. Здесь тоже для вас нет ничего нового: btn1 = new Button("Button North"); btn2 = new Button("Button East"); btn3 = new Button("Button West"); btn4 = new Button("Button South"); btn5 = new Button("Button Center"); Далее мы устанавливаем режим размещения компонент в окне контейнера, вызывая для этого метод setLayout: setLayout(new BorderLayout()); В качестве параметра методу setLayout передается ссылка на только что созданный объект класса BorderLayout. Так как выбран конструктор класса BorderLayout без параметров, зазор между компонентами будет отсутствовать. Добавление компонент выполняется методом add с указанием направления расположения компоненты: add("North", btn1); add("East", btn2); add("West", btn3); add("South", btn4); add("Center", btn5); Заметим, что нельзя размещать несколько компонент в одном и том же направлении. Режим CardLayout Режим CardLayout предназначен для создания набора диалоговых панелей, которые можно показывать по очереди в одном окне прямоугольной формы. Обычно для управления процессом перебора диалоговых панелей в режиме CardLayout используются отдельные органы управления, расположенные в другой панели или даже в другом аплете на той же самой странице сервера WWW. Пример такого аплета мы приведем после того как рассмотрим использование класса Panel. Класс CardLayout содержит два конструктора и несколько методов: public class java.awt.CardLayout extends java.lang.Object implements java.awt.LayoutManager { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Режим без зазоров public CardLayout(); // Режим с зазорами по вертикали и горизонтали // между компонентами и окном контейнера public CardLayout(int hgap, int vgap); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление компоненты с указанием имени public void addLayoutComponent(String name, Component comp); // Отображение первой страницы блокнота public void first(Container target); // Отображение последней страницы блокнота public void last(Container target); // Отображение следующей страницы блокнота public void next(Container target); // Отображение предыдущей страницы блокнота public void previous(Container target); // Выполнение размещения компонент public void layoutContainer(Container target); // Определение минимальных размеров окна, // необходимых для размещения компонент public Dimension minimumLayoutSize(Container target); // Определение предпочтительных размеров окна, // необходимых для размещения компонент public Dimension preferredLayoutSize(Container target); // Удаление заданной компоненты public void removeLayoutComponent(Component comp); // Отображение произвольной страницы блокнота // по ее имени public void show(Container target, String name); // Получение текстовой строки названия режима размещения public String toString(); } Как пользоваться режимом размещения CardLayout? Обычно в окне аплета создается две панели, одна из которых предназначена для отображения страниц блокнота в режиме размещения CardLayout, а вторая содержит органы управления перелистыванием страниц, например, кнопки. Такие методы, как first, last, next и previous позволяют отображать, соответственно, первую, последнюю, следующую и предыдущую страницу блокнота. Если вызвать метод next при отображении последней страницы, в окне появится первая страница. Аналогично, при вызове метода previous для первой страницы блокнота вы увидите последнюю страницу. А как отобразить произвольную страницу, не перебирая их по одной методами next и previous? Для этого существует метод show. Учтите, что этот метод позволяет отображать только такие страницы, при добавлении которых методом add было указано имя, например: pCardPanel.add("BackgroundColor", pBackgroundColor); pCardPanel.add("ForegroundColor", pForegroundColor); pCardPanel.add("Font", pFont); Здесь в панель pCardPanel добавляются панели pBackgroundColor, pForegroundColor и pFont, имеющие имена, соответственно, "BackgroundColor", "ForegroundColor" и "Font". Режим GridBagLayout Режим GridBagLayout намного сложнее только что описанного режима GridLayout. Он позволяет размещать компоненты разного размера в таблице, задавая при этом для отдельных компонент размеры отступов и количество занимаемых ячеек. В нашей книге мы не будем рассматривать этот режим, так как сходные результаты могут быть достигнуты другими, менее сложными способами. Например, вы можете создать в контейнере несколько панелей, использовав внутри каждой свой метод размещения компонент. Если вы создаете аплеты для размещения в документах HTML, никто не заставляет вас ограничиваться только одним аплетом для одного документа HTML - вы можете разместить там произвольное количество аплетов, организовав взаимодействие с одной стороны, между отдельными аплетами, а с другой - между аплетами и расширениями сервера WWW. Тех, кого интересует режим GridBagLayout, мы адресуем к документации, которая входит в комплект Microsoft Visual J++, а также к книге Д. Родли “Создание Java-апплетов”, которая издана на русском языке. В интегрированной системе разработки приложений Java Microsoft Visual J++ версии 1.1 имеется система автоматизированного проектирования пользовательского интерфейса, в результате работы которой создаются исходные тексты классов. Размещение органов управления при этом выполняется интерактивными средствами, аналогичными средствам разработки диалоговых панелей для приложений Microsoft Windows. В следующем томе “Библиотеки системного программиста”, посвященном Java, мы научим вас пользоваться этой системой. 7 РАБОТА С ПАНЕЛЯМИ Панели, создаваемые на базе класса Panel, являются мощным средством организации диалогового интерфейса. Так как класс Panel произошел от класса Container, панель может содержать компоненты и другие панели. Для каждой панели можно определить режим размещения компонент, что позволяет создавать достаточно сложный пользовательский интерфейс. В окне аплета вы можете создать несколько панелей, разделяющих его на части. В свою очередь, пространство, занимаемое панелями, также может быть разделено с использованием одного из описанных выше режимов размещения (рис. 7.1). Рис. 7.1. Размещение нескольких панелей в окне аплета Отдельные панели могут содержать в себе такие компоненты, как кнопки, переключатели, списки, текстовые поля и так далее. Создание панелей Панель создается очень просто. Прежде всего необходимо выбрать для окна аплета схему размещения компонент, соответствующую требуему расположению панелей. Например, для создания в окне аплета двух панелей, разделяющих его по горизонтали, следует выбрать режим GridLayout: setLayout(new GridLayout(2, 1)); Панели будут размещаться в ячейках таблицы, состоящей из одного столбца и двух строк. Далее нужно создать объекты класса Panel: Panel pTopPanel; pTopPanel = new Panel(); Panel pBottomPanel; pBottomPanel = new Panel(); Ссылка на панель, которая будет располагаться сверху, записывается в переменную pTopPanel, а на ту, что будет располагаться снизу - в переменную pBottomPanel. Добавление панелей Создав панели, вы можете добавить их в окно аплета, вызвав метод add, как это показано ниже: add(pTopPanel); add(pBottomPanel); Заметим, что вы можете добавлять панели в панели, указывая, для какой панели нужно вызывать метод add: Panel pLeft; Panel pRight; pLeft = new Panel(); pRight = new Panel(); pTopPanel.setLayout(new GridLayout(1, 2)); pTopPanel.add(pLeft); pTopPanel.add(pRight); Здесь мы создали две панели pLeft и pRight, которые по нашему замыслу должны разделить пространство панели pTopPanel на две части по вертикали. Для обеспечения вертикального размещения панелей pLeft и pRight в панели pTopPanel мы вызвали для панели pTopPanel метод setLayout. При этом мы указали, что компоненты, добавляемые в эту панель, должны размещаться в таблице, состоящей из односй строки и двух столбцов. Затем панели pLeft и pRight были добавлены в панель pTopPanel методом add. Добавление компонент в панели Для добавления компонент в панель вы должны указать, для какой панели вызывается метод add, например: Botton btn1; Botton btn2; btn1 = new Button(); btn2 = new Button(); pBottomPanel.add(btn1); pBottomPanel.add(btn2); Рисование в окне панели Как вы знаете, для того чтобы что-нибудь нарисовать, необходимо вначале получить контекст отображения. Методу paint передается контекст отображения, связанный с окном аплета. Если в окне имеются панели, то для рисования внутри них необходимо получить контекст отображения окон панелей. Проще всего это сделать с помощью метода getGraphics, вызвав его для объекта класса Panel: Graphics gpDraw; gpDraw = pDraw.getGraphics(); Здесь в переменную gpDraw мы записали ссылку на контекст отображения для панели pDraw. Получив контекст отображения, можно приступить к рисованию. Вот, например, как можно нарисовать вокруг панели тонкую рамку: Dimension dimAppWndDimension = pDraw.size(); gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); В этом фрагменте кода мы вначале определили размеры панели, вызвав для нее метод size, а затем при помощи метода drawRect, вызванного для контекста отображения gpDraw, нарисовали рамку. Для установки шрифта и рисования текста в окне панели вы также должны указывать ссылку на контекст отображения вашей панели: gpDraw.setFont(new Font(“Courier”, Font.PLAIN, 12)); gpDraw.drawString("Текст внутри окна панели", 10, 50); Другой способ основан на создании собственного класса на базе класса Panel и переопределения в этом классе метода paint. Мы рассмотрим его позже в разделе “Переопределение класса Panel”. Приложение PanelDemo В приложении PanelDemo мы создаем две панели, расположенные горизонтально. Первая из них используется как блокнот, на каждой странице которого находится кнопка, вторая содержит две управляющие кнопки, позволяющие перебирать страницы блокнота по очереди (рис. 7.2). Рис. 7.2. Окно аплета PanelDemo Объемное изображение схемы расположения панелей и кнопок относительно окна аплета показано на рис. 7.3. Рис. 7.3. Объемное изображение схемы расположения панелей и кнопок В верхней панели друг над другом располагаются пять кнопок (как колода карт), причем видна только одна из них. В нижней панели только две кнопки, с помощью которых можно выдвигать на передний план по очереди все кнопки из верхней панели. Нажимая на кнопки Next и Prev, попробуйте понажимать на кнопки в верхней панели. В строке состояния навигатора при этом будет отображаться название нажатой кнопки (на рис. 6.5 это не показано). Исходные тексты приложения Файл исходного текста приложения PanelDemo представлен в листинге 7.1. Листинг 7.1. Файл PanelDemo\PanelDemo.java // ========================================================= // Работа с панелями класса Panel // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class PanelDemo extends Applet { // Панель для размещения блокнота Panel pCardPanel; // Панель для размещения кнопок управления блокнотом Panel pButtonPanel; // Создаем ссылки на объекты типа Button Button btn1; Button btn2; Button btn3; Button btn4; Button btn5; Button btnNext; Button btnPrev; // Строка для записи названия нажатой кнопки String sTextLabel; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Grid\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.1"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем желтый цвет фона setBackground(Color.yellow); // Создаем в окне аплета две панели, разделяющие // окно по горизонтали. В верхней панели будет // находиться блокнот, // в нижней - кнопки управления блокнотом setLayout(new GridLayout(2, 1)); // Создаем кнопки блокнота btn1 = new Button("Button 1"); btn2 = new Button("Button 2"); btn3 = new Button("Button 3"); btn4 = new Button("Button 4"); btn5 = new Button("Button 5"); // Создаем панель блокнота pCardPanel = new Panel(); // Устанавливаем режим размещения для блокнота pCardPanel.setLayout(new CardLayout(5, 5)); // Добавляем кнопки в блокнот pCardPanel.add(btn1); pCardPanel.add(btn2); pCardPanel.add(btn3); pCardPanel.add(btn4); pCardPanel.add(btn5); // Добавляем панель блокнота в окно аплета add(pCardPanel); // Создаем кнопки управления блокнотом // Кнопка просмотра следующей страницы блокнота btnNext = new Button("Next"); // Кнопка просмотра предыдущей страницы блокнота btnPrev = new Button("Prev"); // Создаем панель кнопок управления блокнотом pButtonPanel = new Panel(); // Устанавливаем режим размещения для панели кнопок pButtonPanel.setLayout(new FlowLayout()); // Добавляем кнопки в панель кнопок pButtonPanel.add(btnNext); pButtonPanel.add(btnPrev); // Добавляем панель кнопок add(pButtonPanel); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Получаем название кнопки sTextLabel = btn.getLabel(); // Записываем название кнопки // в строку состояния навигатора showStatus("Button (\"" + sTextLabel + "\") pressed"); // Выполняем ветвление по кнопкам. Для каждой кнопки // записываем ее название // в строку состояния навигатора if(evt.target.equals(btnNext)) { // Выбираем следующую страницу в блокноте ((CardLayout)pCardPanel.getLayout()).next(pCardPanel); } else if(evt.target.equals(btnPrev)) { // Выбираем предыдущую страницу в блокноте ((CardLayout)pCardPanel.getLayout()).previous(pCardPanel); } else if(evt.target.equals(btn1)) { showStatus("Button 1 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn2)) { showStatus("Button 2 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn3)) { showStatus("Button 3 (\"" + sTextLabel + "\") pressed"); } else if(evt.target.equals(btn4)) { showStatus("Button 4 (\"" + sTextLabel + "\") pressed"); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } } В листинге 7.2 вы найдете исходный текст документа HTML, созданного для размещения нашего аплета. Листинг 7.2. Файл PanelDemo\PanelDemo.html PanelDemo

The source. Описание исходного текста Приведем описание полей и методов, определенных в нашем аплете. Поля класса PanelDemo В поле pCardPanel хранится ссылка на панель блокнота, страницы которого содержать кнопки. Эта панель располагается в верхней части окна аплета. Поле pButtonPanel предназначено для хранения ссылки на панель кнопок, предназначенных для перелистывания страниц блокнота pCardPanel. Ссылки на кнопки, расположенные на страницах блокнота, хранятся в полях btn1, btn2, btn3, btn4 и btn5. Ссылки на кнопки, предназначенные для перелистывания страниц блокнота, записываются в поля btnNext и btnPrev (соответственно, кнопка пролистывания блокнота в прямом и обратном направлении). В поле sTextLabel хранится название нажатой кнопки. Это название отображается в строке состояния навигатора, когда пользователь нажимает какую-либо кнопку в верхней или нижней панели. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете и не имеет никаких особенностей. Метод init Метод init создает все необходимые панели и добавляет в них компоненты. В самом начале своей работы метод init устанавливает желтый цвет фона для окна аплета: setBackground(Color.yellow); После этого выбирается такой режим размещения компонентов, при котором они добавляются в таблицу, состоящую из двух строк и одного столбца: setLayout(new GridLayout(2, 1)); Мы будем добавлять панели, поэтому первая из добавленных панелей окажется в верхней строке этой таблицы и займет верхнюю половину окна аплета, а вторая - нижнюю. Далее метод init создает пять кнопок, которые будут добавлены на страницы блокнота, расположенного в верхней панели: btn1 = new Button("Button 1"); btn2 = new Button("Button 2"); btn3 = new Button("Button 3"); btn4 = new Button("Button 4"); btn5 = new Button("Button 5"); После создания кнопок метод init приступает к созданию и заполнению панели блокнота. Панель создается при помощи конструктора класса Panel: pCardPanel = new Panel(); Для этой панели устанавливается режим размещения компонент типа CardLayout, причем между границами окна панели и границами окна добавляемых компонент по вертикали и горизонтали оставлен зазор 5 пикселов: pCardPanel.setLayout(new CardLayout(5, 5)); После установки режима добавления можно заполнять блокнот кнопками: pCardPanel.add(btn1); pCardPanel.add(btn2); pCardPanel.add(btn3); pCardPanel.add(btn4); pCardPanel.add(btn5); Здесь мы воспользовались известным вам методом add, вызвав его для объекта pCardPanel. Заполненная панель блокнота при помощи все того же метода add добавляется в окно аплета: add(pCardPanel); Так как эта панель добавляется в окно аплета первой, она займет верхнюю половину этого окна. Завершив с панелью блокнота, метод init приступает к формированию панели управляющих кнопок. Вначале метод создает сами управляющие кнопки: btnNext = new Button("Next"); btnPrev = new Button("Prev"); Первая из них перелистывает страницы блокнота в прямом направлении, а вторая - в обратном. Затем создается панель кнопок: pButtonPanel = new Panel(); Для панели кнопок мы выбираем режим выравнивания FlowLayout, при котором компоненты добавляются слева направо и сверху вниз: pButtonPanel.setLayout(new FlowLayout()); После этого в панель добавляются две управляющие кнопки, преднаазначенные для перелистывания страниц блокнота: pButtonPanel.add(btnNext); pButtonPanel.add(btnPrev); На завершающем этапе своей работы метод init добавляет панель управляющих кнопок в окно аплета: add(pButtonPanel); Данная панель добавляется в окно аплета второй по счету, поэтому она будет расположена в нижней половине этого окна. Метод action Метод action обрабатывает события, связанные с кнопками, расположенными в обеих панелях. Вначале метод проверяет, что событие создано кнопкой. Далее идентификатор кнопки, вызвавшей событие, записывается в переменную btn: btn = (Button)evt.target; После этого метод action получает название кнопки, сохраняет его в строке sTextLabel, а затем отображает в строке состояния навигатора: sTextLabel = btn.getLabel(); showStatus("Button (\"" + sTextLabel + "\") pressed"); Далее анализируется ссылка на кнопку. Если была нажата одна из управляющих кнопок, происходит перелистывание страниц блокнота, в прямом или обратном направлении: if(evt.target.equals(btnNext)) { ((CardLayout)pCardPanel.getLayout()).next(pCardPanel); } else if(evt.target.equals(btnPrev)) { ((CardLayout)pCardPanel.getLayout()).previous(pCardPanel); } Здесь мы с помощью метода getLayout получаем ссылку на интерфейс системы Layout Manager, установленной для панели pCardPanel, а затем, пользуясь полученной ссылкой, вызываем методы next или previous. Обратите также внимание на необходимость явного приведения типа к классу CardLayout, в котором определены указанные методы. Обработка событий, создаваемых кнопками, которые расположены на страницах блокнота, не имеет никаких особенностей: else if(evt.target.equals(btn1)) { showStatus("Button 1 (\"" + sTextLabel + "\") pressed"); } . . . else if(evt.target.equals(btn4)) { showStatus("Button 4 (\"" + sTextLabel + "\") pressed"); } Название кнопки просто отображается в строке состояния навигатора. Приложение Notebook Приложение Notebook служит в качестве более сложного примера техники работы с панелями. В окне аплета Notebook создаются три панели, расположенные в одном столбце. В верхней панели, имеющей рамку по периметру, рисуется строка текста “Смотри на шрифт, цвет фона и текста!”. Средняя панель представляет собой блокнот, предназначенный для выбора цвета фона, цвета изображения и шрифта для верхней панели. И, наконец, нижняя панель содержит кнопки, позволяющие перелистывать страницы блокнота. На рис. 7.4 показана страница, предназанченная для выбора цвета фона: Рис. 7.4. Страница, предназанченная для выбора цвета фона Нажимая кнопки Background Color, Foreground Color и Set Text Font, вы сможете выдвигать на передний план страницы блокнота, предназначенные, соответственно, для выбора цвета фона, изображения и шрифта, которым отображается текст в верхней панели. Кнопки Next и Prev работают таким же образом, что и в предыдущем приложении, а именно: если нажать на кнопку Next, произойдет пролистывание страниц в прямом направлении, а если на кнопку Prev - в обратном направлении. На рис. 7.5 изображена страница блокнота, предназначенная для выбора цвета изображения. Рис. 7.5. Страница, предназанченная для выбора цвета изображения На рис. 7.6 представлена страница, с помощью которой можно выбрать один из нескольких шрифтов для рисования текста в верхней панели. Рис. 7.6. Страница, предназанченная для выбора шрифта Рассмотрим исходные тексты приложения Notebook/ Исходные тексты приложения Исходный текст приложения Notebook приведен в листинге 7.3. Листинг 7.3. Файл Notebook\Notebook.java // ========================================================= // Набор диалоговых панелей // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Notebook extends Applet { // Панель для размещения блокнота Panel pCardPanel; // Панель для размещения кнопок управления блокнотом Panel pButtonPanel; // Панель для рисования Panel pDraw; // Панели отдельных страниц Panel pBackgroundColor; // страница выбора цвета фона Panel pForegroundColor; // страница выбора цвета // изображения Panel pFont; // страница выбора шрифта // Кнопки выбора сраниц блокнота Button btnNext; // следующая Button btnPrev; // предыдущая Button btnBackgroundColor; // фон Button btnForegroundColor; // изображение Button btnFont; // шрифт // Создаем ссылки на объекты класса Choice Choice chBackgroundColor; // список цветов фона Choice chForegroundColor; // список цветов изображения Choice chFont; // список шрифтов // Текстовые метки списков Label tBackgroundColor; // метка списка цветов фона Label tForegroundColor; // метка списка цветов // изображения Label tFont; // метка списка шрифтов // Строка для хранения название выбранного шрифта String sFontName; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Grid\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.1"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем желтый цвет фона setBackground(Color.yellow); // ---------------------------------------------------- // Создаем в окне аплета две панели, разделяющие // окно по горизонтали. В верхней панели будет // находиться блокнот, в нижней - кнопки управления // блокнотом // ---------------------------------------------------- setLayout(new GridLayout(3, 1)); // Создаем панель блокнота pCardPanel = new Panel(); // ---------------------------------------------------- // Создаем панели страниц блокнота // ---------------------------------------------------- // Панель для выбора цвета фона pBackgroundColor = new Panel(); // Панель для выбора цвета изображения pForegroundColor = new Panel(); // Панель для выбора шрифта pFont = new Panel(); // ---------------------------------------------------- // Создаем списки для выбора цвета фона и // цвета изображения // ---------------------------------------------------- // Список для выбора цвета фона chBackgroundColor = new Choice(); // Список для выбора цвета изображения chForegroundColor = new Choice(); // Список для выбора шрифта chFont = new Choice(); // ---------------------------------------------------- // Создаем метки для списков // ---------------------------------------------------- // Метка для списка цвета фона tBackgroundColor = new Label("Chose Background Color:"); // Метка для списка цвета изображения tForegroundColor = new Label("Chose Foreground Color:"); // Метка для списка шрифтов tFont = new Label("Chose Font:"); // ---------------------------------------------------- // Добавляем списки и метки в панели // ---------------------------------------------------- // Список цвета фона и метка pBackgroundColor.add(tBackgroundColor); pBackgroundColor.add(chBackgroundColor); // Список цвета изображения и метка pForegroundColor.add(tForegroundColor); pForegroundColor.add(chForegroundColor); // Список шрифтов и метка pFont.add(tFont); pFont.add(chFont); // ---------------------------------------------------- // Заполняем списки // ---------------------------------------------------- // Заполняем список цвета фона chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); // Заполняем список цвета изображения chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); // Заполняем список шрифтов chFont.addItem("Helvetica"); chFont.addItem("Courier"); chFont.addItem("TimesRoman"); // Создаем панель для рисования pDraw = new Panel(); // Устанавливаем режим размещения для блокнота pCardPanel.setLayout(new CardLayout(5, 5)); // ---------------------------------------------------- // Добавляем панели страниц в блокнот // ---------------------------------------------------- // Панель выбора цвета фона pCardPanel.add("BackgroundColor", pBackgroundColor); // Панель выбора цвета изображения pCardPanel.add("ForegroundColor", pForegroundColor); // Панель выбора шрифта pCardPanel.add("Font", pFont); // Добавляем панель для рисования в окно аплета add(pDraw); // Добавляем панель блокнота в окно аплета add(pCardPanel); // ---------------------------------------------------- // Создаем кнопки управления блокнотом // ---------------------------------------------------- // Кнопка просмотра следующей страницы блокнота btnNext = new Button("Next"); // Кнопка просмотра предыдущей страницы блокнота btnPrev = new Button("Prev"); // Выбор панели цвета фона btnBackgroundColor = new Button("Background Color"); // Выбор панели цвета изображения btnForegroundColor = new Button("Foreground Color"); // Выбор панели шрифтов btnFont = new Button("Set Text Font"); // Создаем панель кнопок управления блокнотом pButtonPanel = new Panel(); // Устанавливаем режим размещения для панели кнопок pButtonPanel.setLayout(new FlowLayout()); // Добавляем кнопки в панель кнопок pButtonPanel.add(btnBackgroundColor); pButtonPanel.add(btnForegroundColor); pButtonPanel.add(btnFont); pButtonPanel.add(btnNext); pButtonPanel.add(btnPrev); // Добавляем панель кнопок add(pButtonPanel); // Выбираем шрифт по умолчанию sFontName = new String("Helvetica"); // Отображаем окно аплета show(); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Выполняем ветвление по кнопкам. Для каждой кнопки // записываем ее название в строку состояния // навигатора if(evt.target.equals(btnNext)) // Выбираем следующую страницу в блокноте ((CardLayout)pCardPanel.getLayout()).next(pCardPanel); else if(evt.target.equals(btnPrev)) // Выбираем предыдущую страницу в блокноте ((CardLayout)pCardPanel.getLayout()).previous(pCardPanel); else if(evt.target.equals(btnBackgroundColor)) // Выбираем страницу цвета фона ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "BackgroundColor"); else if(evt.target.equals(btnForegroundColor)) // Выбираем страницу цвета изображения ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "ForegroundColor"); else if(evt.target.equals(btnFont)) // Выбираем страницу шрифтов ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "Font"); // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем, передавая методу action // родительского класса else return super.action(evt, obj); // Перерисовываем окно панели pDraw и аплета pDraw.repaint(); repaint(); // Возвращаем признак того, что мы обработали событие return true; } // Обработка событий от списков else if(evt.target instanceof Choice) { // Переменная для хранения ссылки на список, // вызвавший событие Choice ch; // Получаем ссылку на список ch = (Choice)evt.target; // Выполняем ветвление по спискам // Список цвета фона if(evt.target.equals(chBackgroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет фона if(ch.getSelectedIndex() == 0) pDraw.setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) pDraw.setBackground(Color.green); else if(ch.getSelectedIndex() == 2) pDraw.setBackground(Color.white); } // Список цвета изображения else if(evt.target.equals(chForegroundColor)) { // Получаем номер текущего элемента списка // и устанавливаем соответствующий // цвет изображения if(ch.getSelectedIndex() == 0) pDraw.setForeground(Color.black); else if(ch.getSelectedIndex() == 1) pDraw.setForeground(Color.red); else if(ch.getSelectedIndex() == 2) pDraw.setForeground(Color.blue); } // Список шрифтов else if(evt.target.equals(chFont)) { // Получаем номер текущего элемента списка // и записываем имя соответствующего шрифта // в строку sFontName if(ch.getSelectedIndex() == 0) sFontName = "Helvetica"; else if(ch.getSelectedIndex() == 1) sFontName = "Courier"; else if(ch.getSelectedIndex() == 2) sFontName = "TimesRoman"; } // Если событие возникло от неизвестного списка, // мы его не обрабатываем, передавая методу action // родительского класса else return super.action(evt, obj); // Перерисовываем панель pDraw pDraw.repaint(); // Перерисовываем окно аплета repaint(); // Возвращаем признак того, что мы обработали событие return true; } // Вызываем метод action родительского класса return super.action(evt, obj); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { Graphics gpDraw; // Получаем контекст отображения для панели рисования gpDraw = pDraw.getGraphics(); // Определяем текущие размеры Dimension dimAppWndDimension = pDraw.size(); // Рисуем рамку вокруг окна аплета gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Устанавливаем шрифт gpDraw.setFont(new Font(sFontName, Font.PLAIN, 12)); // Рисуем строку gpDraw.drawString( "Смотри на шрифт, цвет фона и текста!", 10, 50); // Получаем контекст отображения для панели блокнота gpDraw = pCardPanel.getGraphics(); // Определяем размеры панели блокнота dimAppWndDimension = pCardPanel.size(); // Обводим блокнот рамкой gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } } В листинге 7.4 вы найдете исходный текст документа HTML, созданного для размещения аплета. Листинг 7.4. Файл Notebook\Notebook.html Notebook

The source. Описание исходного текста В классе Notebook определено довольно много полей и переопределено несколько методов. Поля класса Notebook В полях pDraw, pCardPanel и pButtonPanel находятся ссылки, соответственно, на верхнюю, среднюю и нижнюю панели, предназначенные для рисования, размещения блокнота диалоговых панелей настроек и кнопок управления блокнотом. В предыдущем приложении на страницах блокнота размещались кнопки. Теперь мы решили более сложную задачу - поместили на страницы блокнота три панели, по одной на каждую страницу. Первая из этих панелей содержит список для выбора цвета фона, вторая - для выбора цвета изображения и, наконец, третья, для выбора шрифта. Поля pBackgroundColor, pForegroundColor и pFont хранят ссылки на соответствующие панели настроек. Нижняя панель содержит кнопки управления страницами блокнота. С помощью кнопок, ссылки на которые хранятся в полях btnBackgroundColor, btnForegroundColor и btnFont вы можете выбирать для отображения страницы блокнота, содержащие панели настройки цвета фона, изображения и шрифта. Таким образом, нет необходимости перебирать страницы блокнота по очереди до тех пор, пока в окне не появится нужная страница. Тем не менее, мы предусмотрели кнопки и для циклического перебора страниц блокнота. Ссылки на эти кнопки хранятся в полях btnNext и btnPrev. На каждой панели в блокноте размещается один список и одна надпись, объясняющая назначение списка. Списки создаются как объекты класса Choice, а надписи - как объекты класса Label. Поля chBackgroundColor, chForegroundColor и chFont хранят ссылки на списки, соответственно, цвета фона, цвета изображения и шрифтов. В полях tBackgroundColor, tForegroundColor и tFont хранятся ссылки надписей. Поле sFontName класса String предназначено для хранения названия текущего шрифта, с использованием которого отображается текст в верхней панели. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете. Метод init Метод init выполняет достаточно громоздкую работу по созданию и добавлению различных панелей и других компонентов. К сожалению, приложениям Java не доступны ресурсы, аналогичные ресурсам операционной системы Microsoft Windows, поэтому формирование диалоговых панелей и других элементов пользовательского интерфейса приходится выполнять чисто программными методами на этапе выполнения приложения. Средства среды разработки приложений Java Microsoft Visual J++ версии 1.1, о которых мы уже упоминали, позволяют несколько упростить этот процесс. Свою работу метод init начинает с установки желтого цвета фона для окна аплета. Далее устанавливается режим добавления GridLayout, разделяющий окно аплета на три части по горизонтали: setLayout(new GridLayout(3, 1)); Соответствующая таблица, в которую будут добавляться компоненты, имеет три строки и один столбец. Панель блокнота создается следующим образом: pCardPanel = new Panel(); Затем создаются три панели, которые будут добавляться в панель pCardPanel: pBackgroundColor = new Panel(); pForegroundColor = new Panel(); pFont = new Panel(); Эти панели предназначены для размещения компонент, с помощью которых можно будет выбирать цвет фона и изображения, а также шрифт. На следующем этапе создаются три списка, которые будут размещаться по одному на указанных панелях: chBackgroundColor = new Choice(); chForegroundColor = new Choice(); chFont = new Choice(); Каждый такой список снабжается надписью, поясняющей его назначение. Надписи создаются следующим образом: tBackgroundColor = new Label("Chose Background Color:"); tForegroundColor = new Label("Chose Foreground Color:"); tFont = new Label("Chose Font:"); Созданные метки и списки добавляются в панели, расположенные на страницах блокнота: pBackgroundColor.add(tBackgroundColor); pBackgroundColor.add(chBackgroundColor); pForegroundColor.add(tForegroundColor); pForegroundColor.add(chForegroundColor); pFont.add(tFont); pFont.add(chFont); Вначале мы добавляем надпись, затем список. Поэтому надпись будет расположена слева от списка. Заполнение списков выполняется с помощью метода addItem, который вызывается по очереди для каждого списка и для каждого добавляемого элемента списка: chBackgroundColor.addItem("Yellow"); chBackgroundColor.addItem("Green"); chBackgroundColor.addItem("White"); chForegroundColor.addItem("Black"); chForegroundColor.addItem("Red"); chForegroundColor.addItem("Blue"); chFont.addItem("Helvetica"); chFont.addItem("Courier"); chFont.addItem("TimesRoman"); Далее метод init приступает к формированию верхенй панели, предназначенной для отображения текстовой строки. Панель создается следующим образом: pDraw = new Panel(); Затем метод init начинает наполнение страниц блокнота. Прежде всего он устанавливает режим добавления CardLayout, оставляя зазор по вертикали и горизонтали в 5 пикселов: pCardPanel.setLayout(new CardLayout(5, 5)); Панели добавляются методом add, причем мы выбрали вариант этого метода, допускающий присваивание имен добавляемым компонентам: pCardPanel.add("BackgroundColor", pBackgroundColor); pCardPanel.add("ForegroundColor", pForegroundColor); pCardPanel.add("Font", pFont); Имена, которые передаются через первый параметр, необходимы для отображения страниц блокнота с помощью кнопок btnBackgroundColor, btnForegroundColor и btnFont. После заполнения страниц блокнота метод init добавляет верхнюю и среднюю панели в окно аплета: add(pDraw); add(pCardPanel); В процессе формирования нижней панели метд init создает кнопки, предназначенные для управления блокнотом: btnNext = new Button("Next"); btnPrev = new Button("Prev"); btnBackgroundColor = new Button("Background Color"); btnForegroundColor = new Button("Foreground Color"); btnFont = new Button("Set Text Font"); pButtonPanel = new Panel(); Перед добавлением кнопок в нижней панели устанавливается режим размещения FlowLayout: pButtonPanel.setLayout(new FlowLayout()); Затем кнопки добавляются в панель вызовом метода add: pButtonPanel.add(btnBackgroundColor); pButtonPanel.add(btnForegroundColor); pButtonPanel.add(btnFont); pButtonPanel.add(btnNext); pButtonPanel.add(btnPrev); После формирования панели кнопок эта панель добавляется в окно аплета, располагаясь в его нижней части: add(pButtonPanel); В поле sFontName записывается имя шрифта, выбранного по умолчанию: sFontName = new String("Helvetica"); На завершающем этапе метод init выполняет принудительную перерисовку и отображение панелей, вызывая специально предназначенный для этого метод show: show(); Метод action Метод action выполняет раздельную обработку событий, вызванных кнопками и списками. Если событие было вызвано кнопками, выполняется переключение страниц блокнота: if(evt.target.equals(btnNext)) ((CardLayout)pCardPanel.getLayout()).next(pCardPanel); else if(evt.target.equals(btnPrev)) ((CardLayout)pCardPanel.getLayout()).previous(pCardPanel); else if(evt.target.equals(btnBackgroundColor)) ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "BackgroundColor"); else if(evt.target.equals(btnForegroundColor)) ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "ForegroundColor"); else if(evt.target.equals(btnFont)) ((CardLayout)pCardPanel.getLayout()).show( pCardPanel, "Font"); Для выбора следующей и предыдущей страницы здесь использованы методы next и previous. Выбор конкретной страницы по ее имени осуществляется с помощью метода show. В качестве параметров этому методу передается ссылка на панель блокнота и имя страницы. Обратите также внимание на способ обработки событий, не имеющих отношения к нашим компонентам: return super.action(evt, obj); Здесь мы вызываем метод action из базового класса, который после соответствующей обработки события вернет значение true или false. Если событие вызвано кнопками управления блокнотом, мы перерисовываем окно верхней панели, окно всего аплета и затем возвращаем признак успешной обработки события: pDraw.repaint(); repaint(); return true; Как вы увидите дальше, в процессе перерисовки окна всего аплета метод paint выполнит рисование в окне верхней панели. События, связанные с выбором нового цвета фона и изображения обрабатываются следующим образом: if(evt.target.equals(chBackgroundColor)) { if(ch.getSelectedIndex() == 0) pDraw.setBackground(Color.yellow); else if(ch.getSelectedIndex() == 1) pDraw.setBackground(Color.green); else if(ch.getSelectedIndex() == 2) pDraw.setBackground(Color.white); } else if(evt.target.equals(chForegroundColor)) { if(ch.getSelectedIndex() == 0) pDraw.setForeground(Color.black); else if(ch.getSelectedIndex() == 1) pDraw.setForeground(Color.red); else if(ch.getSelectedIndex() == 2) pDraw.setForeground(Color.blue); } Здесь методы setBackground и setForeground устанавливают цвет фона и изображения для панели pDraw. Если событие вызвано списком шрифтов, то мы получаем номер элемента списка и записываем название выбранного шрифта в текстовую строку sFontName: else if(evt.target.equals(chFont)) { if(ch.getSelectedIndex() == 0) sFontName = "Helvetica"; else if(ch.getSelectedIndex() == 1) sFontName = "Courier"; else if(ch.getSelectedIndex() == 2) sFontName = "TimesRoman"; } После обработки события, вызванного списками, мы перерисовываем окно панели рисования pDraw и окно аплета: pDraw.repaint(); repaint(); Метод paint Метод paint рисует в окне панели pDraw, а не в главном окне аплета. Для этого метод paint получает контекст отображения этой панели, вызывая для этого метод getGraphics: Graphics gpDraw; gpDraw = pDraw.getGraphics(); Далее метод paint определяет размеры панели pDraw и рисует рамку вокруг окна этой панели: Dimension dimAppWndDimension = pDraw.size(); gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); После того как рамка будет нарисована, метод paint устанавливает в панели pDraw шрифт, название которого хранится в староке sFontName. Для этого используется контекст отображения gpDraw, полученный нами ранее для панели pDraw: gpDraw.setFont(new Font(sFontName, Font.PLAIN, 12)); Текстовая строка отображается с использованием текущего цвета изображения и текущего шрифта, установленного в контексте отображения gpDraw, при помощи метода drawString: gpDraw.drawString("Смотри на шрифт, цвет фона и текста!", 10, 50); Затем метод paint обводит панель блокнота рамкой. Вначале он получает контекст отображения для панели блокнота, как это показано ниже: gpDraw = pCardPanel.getGraphics(); После получения контекста отображения метод paint определяет размеры панели блокнота и рисует рамку вокруг его окна: dimAppWndDimension = pCardPanel.size(); gpDraw.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Создание нового класса на базе класса Panel Если ваш аплет создает много панелей, техника рисования в окнах этих панелей, использованная в только что рассмотренном приложении Notebook, может привести к усложнению исходного текста приложения. Так как рисование в окнах панелей выполняется в методе paint класса аплета, вам придется получать контекст отображения для каждой панели. Намного проще создать несколько дочерних классов от класса Panel, переопределив в каждом из них метод paint. В этом случае для каждой панели вы можете создать свой метода paint, которому будет автоматически передаваться контекст отображения, связанный с окном соответствующей панели. Эта техника использована в приложении Panel2, которое мы рассмотрим в следующем разделе. Приложение Panel2 В окне аплета Panel2 мы создали две панели, одна из которых занимает верхнюю половину окна, а другая - нижнюю (рис. 7.7). Рис. 7.7. Окно аплета Panel2 с двумя панелями Для каждой панели мы создали два отдельных класса на базе класса Panel. В созданных нами классах переопределен метод paint, который рисует одну текстовую строку в окне своей панели. В верхней панели рисуется строка “Первая панель”, в нижней - “Вторая панель”. Кроме того, метод paint класса нашего аплета рисует две строки в окнах обеих панелей, получая контекст отображения для панелей и указывая ссылку на этот контекст явным образом. В процессе инициализации метод init класса нашего аплета устанавливает различный цвет фона и изображения для панелей, поэтому рамки вокруг окон панелей и текст получаются нарисованными различным цветом независимо от способа их рисования. Исходные тексты приложения Файл исходных текстов приложения приведен в листинге 7.5. Листинг 7.5. Файл Panel2\Panel2.java // ========================================================= // Работа с панелями класса Panel // Наследование от класса Panel // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // ========================================================= // Класс Panel2 // Это наш аплет // ========================================================= public class Panel2 extends Applet { // Первая панель FirstPanel pPanel1; // Вторая панель SecondPanel pPanel2; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Panel2\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем в окне аплета две панели, разделяющие // окно по горизонтали setLayout(new GridLayout(2, 1)); // Создаем первую панель pPanel1 = new FirstPanel(); // Добавляем первую панель в окно аплета add(pPanel1); // Создаем вторую панель pPanel2 = new SecondPanel(); // Добавляем вторую панель add(pPanel2); // Устанавливаем желтый цвет фона для первой панели pPanel1.setBackground(Color.yellow); // Устанавливаем черный цвет изображения // для первой панели pPanel1.setForeground(Color.black); // Устанавливаем белый цвет фона для второй панели pPanel2.setBackground(Color.white); // Устанавливаем красный цвет изображения // для второй панели pPanel2.setForeground(Color.red); // Инициируем вызов метода paint repaint(); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { Graphics gPanel1; Graphics gPanel2; // Получаем контекст отображения для первой панели gPanel1 = pPanel1.getGraphics(); // Рисуем строку в окне первой панели gPanel1.drawString("Нарисовано в первой панели", 10, 80); // Получаем контекст отображения для второй панели gPanel2 = pPanel2.getGraphics(); // Рисуем строку в окне второй панели gPanel2.drawString("Нарисовано во второй панели", 10, 80); } } // ========================================================= // Класс FirstPanel // Первая панель // ========================================================= class FirstPanel extends Panel { // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры Dimension dimAppWndDimension = size(); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Устанавливаем шрифт g.setFont(new Font("TimesRoman", Font.PLAIN, 12)); // Рисуем строку g.drawString("Первая панель", 10, 50); // Вызываем метод paint родительского класса super.paint(g); } } // ========================================================= // Класс SecondPanel // Вторая панель // ========================================================= class SecondPanel extends Panel { // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры Dimension dimAppWndDimension = size(); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Устанавливаем шрифт g.setFont(new Font("Helvetica", Font.PLAIN, 12)); // Рисуем строку g.drawString("Вторая панель", 10, 50); // Вызываем метод paint родительского класса super.paint(g); } } Исходный текст документа HTML, предназначенного для размещения нашего аплета, представлен в листинге 7.6. Листинг 7.6. Файл Panel2\Panel2.html Panel2

The source. Описание исходного текста Как мы уже говорили, в приложении Panel2 мы создали два класса, взяв для них в качестве базового класс Panel. Имена этих классов - FirstPanel и SecondPanel. После трансляции проекта системой Microsoft Visual J++ получаются три двоичных файла с именами Panel2.class, FirstPanel.class и SecondPanel.class - по одному для каждого класса. Поля класса Panel2 В классе Panel2 определено два поля с именами pPanel1 и pPanel2 класса Panel. Первое из них предназначено для хранения ссылки на верхюю панель, второе - на нижнюю (в соответствии с их расположением в окне аплета). Метод getAppletInfo класса Panel2 Метод getAppletInfo возвращает информацию об аплете. Метод init класса Panel2 Прежде всего метод init устанавливает для окна панели режим добавления компонент GridLayout, определяя таблицу из двух строк и одного столбца. Первая панель создается на базе класса FirstPanel, определенного в нашем приложении: pPanel1 = new FirstPanel(); Этот класс мы рассмотрим позже. Созданная панель добавляется в окно аплета методом add: add(pPanel1); Аналогично мы создаем и вторую панель, на этот раз как объект класса SecondPanel: pPanel2 = new SecondPanel(); Вторая панель добавляется в окно аплета точно также, как и первая: add(pPanel2); Для того чтобы выделить панели на фоне окна аплета, мы устанавливаем для них разные цвета фона и изображения. Для первой панели устанавливается желтый цвет фона и черный цвет изображения: pPanel1.setBackground(Color.yellow); pPanel1.setForeground(Color.black); Для второй панели мы устанавливаем белый цвет фона и красный цвет изображения: pPanel2.setBackground(Color.white); pPanel2.setForeground(Color.red); В результате цвета рамки окна и текста первой и второй панели будут разными. На последнем этапе инициализации мы инициируем вызов метода paint класса Panel2, вызывая для этого метод repaint: repaint(); Это нужно для того, чтобы сразу после отображения окна аплета выполнить рисование текстовых строк внутри окон панелей. Метод paint класса Panel2 Метод paint класса Panel2 рисует две строки в окнах панелей, расположенных в окне аплета. Для этого он получает контекст отображения каждой панели и вызывает для этого контекста метод drawGraphics: Graphics gPanel1; Graphics gPanel2; gPanel1 = pPanel1.getGraphics(); gPanel1.drawString("Нарисовано в первой панели", 10, 80); gPanel2 = pPanel2.getGraphics(); gPanel2.drawString("Нарисовано во второй панели", 10, 80); Хотя при рисовании строк мы указали одинаковые координаты начала строки (10, 80), наложения строк не произойдет. Это потому, что эти строки рисуются в разных графических контекстах, которые относятся к окнам разных панелей. Цвет рисуемого текста и фона, на котором он будет нарисован, также получится разный, так как для наших панелей установлен разный цвет изображения и фона. Класс FirstPanel Класс FirstPanel создан на базе класса Panel: class FirstPanel extends Panel { public void paint(Graphics g) { . . . super.paint(g); } } В нашем приложении мы создаем верхнюю панель как объект этого класса. Единственный метод, переопределенный в классе FirstPanel - это метод paint. Метод paint класса FirstPanel Задачей метода paint класса FirstPanel является рисование рамки вокруг первой панели и текстовой строки в окне этой панели. В качестве параметра метод paint класса FirstPanel получает ссылку на контекст отображения для окна первой панели. Мы можем использовать этот контекст для того чтобы нарисовать что-нибудь внутри первой панели. Процедура рисования не имеет никаких особенностей: Dimension dimAppWndDimension = size(); g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); g.setFont(new Font("TimesRoman", Font.PLAIN, 12)); g.drawString("Первая панель", 10, 50); Заметим, однако, что после завершения рисования мы вызываем метод paint из базового класса, позволяя этому классу выполнить свою обработку: super.paint(g); Класс SecondPanel Класс SecondPanel создан также на базе класса Panel: class SecondPanel extends Panel { public void paint(Graphics g) { . . . super.paint(g); } } С использованием этого класса создается нижняя панель. В классе SecondPanel, как и в классе FirstPanel, переопределен метод paint. Метод paint класса SecondPanel Метода paint класса SecondPanel выполняет те же самые операции, что и метод paint класса FirstPanel, однако ему передается контекст отображения второй панели. Поэтому он нарисует рамку и текстовую строку во второй панели, а не в первой или где-нибудь еще. 8 ОКНА И ДИАЛОГОВЫЕ ПАНЕЛИ До сих пор мы рисовали только в окне аплета или в окнах панелей, расположенных внутри окна аплета. Однако есть и другая возможность - приложения Java, полноценные и аплеты, могут создавать обычные перекрывающиеся окна, такие, например, как окно навигатора. Эти окна могут иметь меню (в отличие от окон аплетов). Пользователь может изменять размер таких окон при помощи мыши, перемещая рамку окна. В составе библиотеки классов AWT имеется несколько классов, предназначенных для работы с окнами. Это класс Window, который произошел от класса Container, и его дочерние классы - Frame, Dialog и FileDialog (рис. 8.1). Рис. 8.1. Иерархия классов, предназначенных для создания окон Окно, созданное на базе класса Frame, больше всего похоже на главное окно обычного приложения Windows. Оно может иметь главное меню, для него можно устанавливать форму курсора. Внутри такого окна можно рисовать. Так как окно класса Frame (так же как и другие окна AWT) произошли от класса Container, вы можете добавлять в них различные компоненты и панели, как мы это делали с окнами аплетов и панелей. На базе класса Dialog создаются окна диалоговых панелей, очень похожих на обычные диалоговые панели Windows. Такие панели не могут иметь меню и обычно предназначены для запроса какой-либо информации у пользователя. Класс FileDialog предназначен для создания диалоговых панелей, с помощью которых можно выбирать файлы на локальных дисках компьютера. Так как аплеты не могут работать с файлами, в этой книге мы не будем рассматривать класс FileDialog. Информацию о том, как работать с файлами в приложениях Java, а также сведения об этом классе мы планируем включить в следующий том “Библиотеки системного программиста”, посвященный системе разработки Microsoft Visual J++. Что же касается класса Window, то непосредственно этот класс редко применяется для создания окон, так как классы Frame, Dialog и FileDialog более удобны и обеспечивают все необходимые возможности. Окна класса Frame Ниже мы привели краткое описание класса Frame. Так как этот класс реализует интерфейс java.awt.MenuContainer, окно класса Frame может содержать меню. public class java.awt.Frame extends java.awt.Window implements java.awt.MenuContainer { // ----------------------------------------------------- // Поля // ----------------------------------------------------- // Различные типы курсоров public final static int CROSSHAIR_CURSOR; public final static int DEFAULT_CURSOR; public final static int E_RESIZE_CURSOR; public final static int HAND_CURSOR; public final static int MOVE_CURSOR; public final static int N_RESIZE_CURSOR; public final static int NE_RESIZE_CURSOR; public final static int NW_RESIZE_CURSOR; public final static int S_RESIZE_CURSOR; public final static int SE_RESIZE_CURSOR; public final static int SW_RESIZE_CURSOR; public final static int TEXT_CURSOR; public final static int W_RESIZE_CURSOR; public final static int WAIT_CURSOR; // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание окна без заголовка public Frame(); // Создание окна с заголовоком public Frame(String title); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createFrame public void addNotify(); // Удаление окна и освобождение связанных с ним ресурсов public void dispose(); // Определение типа курсора public int getCursorType(); // Получение пиктограммы, установленной для окна public Image getIconImage(); // Получение ссылки на главное меню public MenuBar getMenuBar(); // Получение заголовка окна public String getTitle(); // Определение возможности изменения // размеров окна пользователем public boolean isResizable(); // Получение строки параметров protected String paramString(); // Удаление компоненты меню public void remove(MenuComponent m); // Установка типа курсора public void setCursor(int cursorType); // Установка пиктограммы public void setIconImage(Image image); // Установка главного меню public void setMenuBar(MenuBar mb); // Включение или выключение возомжности // изменения размеров окна public void setResizable(boolean resizable); // Установка заголовка окна public void setTitle(String title); } Для того чтобы создать свое окно на базе класса Frame, вы должны определить свой класс, унаследовав его от класса Frame следующим образом: class MainFrameWnd extends Frame { . . . public MainFrameWnd(String sTitle) { super(sTitle); . . . resize(400, 200); } . . . } Если мы будем создавать окно с заголовком, нам необходимо соответствующим образом определить конструктор класса этого окна. В частности, наш конструктор должен вызывать конструктор базового класса, передавая ему в качестве параметра строку заголовка окна. Напомним, что конструктор базового класса должен вызываться в конструкторе дочернего класса перед выполнением каких-либо других действий. Обратите также внимание на вызов метода resize. Этот вызов необходим для задания размеров окна. В конструкторе вы можете определить различные параметры создаваемого вами окна, например, указать форму курсора, пиктограмму, представляющую окно, задать меню, определить возможность изменения размеров окна и так далее. Мы остановимся подробнее на процедуре добавления меню к окну класса Frame, так как она требует пояснений. С изменением других характеристик окна вы справитесь самостоятельно. При создании окна классов Frame и Dialog для них устанавливается режим размещения BorderLayout. Если вам нужен другой режим размещения, необходимо установить его явным образом. Кроме того, созданное окно появится на экране только после вызова для него метода show. Убрать окно с экрана вы можете методом hide. Этот метод прячет окно, но оставляет в памяти все связанные с ним ресурсы, поэтому вы сможете вновь отобразить спрятанное окно, вызвав метод show. В отличие от метода hide, метод dispose удаляет окно и освобождает все связанные с ним ресурсы. Этот метод применяется для окончательного удаления окна с экрана и из памяти. Еще одно замечание касается обработки операции уничтожения окна при помощи двойного щелчка левой клавиши мыши по системному меню окна или при помощи кнопки уничтожения окна, расположенной в правой части заголовка. Когда пользователь пытается уничтожить окно класса Frame или Dialog подобным образом, возникает событие Event.WINDOW_DESTROY. Вы должны предусмотреть обработку этого события, выполняя действия, соответствующие логике работы вашего окна. Обычно окно уничтожается вызовом метода dispose, как это показано ниже: public boolean handleEvent(Event evt) { if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt); } Меню в окне класса Frame Как мы уже говорили, окно класса Frame может иметь главное меню (Menu Bar) или, как еще говорят, строку меню. Главное меню создается на базе класса MenuBar, краткое описание которого приведено ниже: public class java.awt.MenuBar extends java.awt.MenuComponent implements java.awt.MenuContainer { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public MenuBar(); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление меню в главное меню окна public Menu add(Menu m); // Вызов метода createMenuBar public void addNotify(); // Определение количества меню, добавленных // в главное меню public int countMenus(); // Получение ссылки на меню Help public Menu getHelpMenu(); // Получение ссылки на меню с заданным номером public Menu getMenu(int i); // Удаление меню с заданным номером из главного меню public void remove(int index); // Удаление компоненты меню public void remove(MenuComponent m); // Извещение об удалении меню public void removeNotify(); // Установка меню Help public void setHelpMenu(Menu m); } Для формирования главного меню окна вы должны создать объект класса MenuBar с помощью конструктора, а затем добавить в него отдельные меню. Объект главного меню создается следующим образом: MenuBar mbMainMenuBar; mbMainMenuBar = new MenuBar(); Отдельные меню создаются на базе класса Menu, например: Menu mnFile; Menu mnHelp; mnFile = new Menu("File"); mnHelp = new Menu("Help"); Создав меню, вы должны добавить в них строки. Для этого нужно вызвать метод add, передав ему в качестве параметра текст строки меню, например: mnFile.add("New"); mnFile.add("-"); mnFile.add("Exit"); mnHelp.add("Content"); mnHelp.add("-"); mnHelp.add("About"); Далее сформированные меню добавляются в главное меню: mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp); И, наконец, теперь можно устанавливать главное меню в окне класса, созданного на базе класса Frame: setMenuBar(mbMainMenuBar); Классы Menu и MenuItem Для того чтобы дать вам представление о том, что можно делать с меню, приведем краткое описание класса Menu: public class java.awt.Menu extends java.awt.MenuItem implements java.awt.MenuContainer { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание меню с заданным названием public Menu(String label); // Создание меню с заданным названием, // которое может оставаться на экране после того как // пользователь отпустил клавишу мыши public Menu(String label, boolean tearOff); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Добавление элемента меню public MenuItem add(MenuItem mi); // Добавление строки в меню public void add(String label); // Вызов метода createMenu public void addNotify(); // Добавление разделителя в меню public void addSeparator(); // Определение количества строк в меню public int countItems(); // Получение ссылки на элемент меню с заданным номером public MenuItem getItem(int index); // Проверка, остается ли меню на экране после того как // пользователь отпустил клавишу мыши public boolean isTearOff(); // Удаление заданного элемента меню public void remove(int index); // Удаление заданной компоненты меню public void remove(MenuComponent item); // Извещение об удалении меню public void removeNotify(); } Метод addSeparator используется для добавления в меню разделительной строки. Аналогичный результат достигается и при добавлении в меню стоки “-“: mnHelp.add("-"); Заметим, что вы можете просто добавлять в меню строки по их названию, пользуясь методом add(String label), либо добавлять в меню элементы класса MenuItem, вызывая метод add(MenuItem mi). Класс MenuItem определяет поведение отдельных элементов меню: public class java.awt.MenuItem extends java.awt.MenuComponent { // ----------------------------------------------------- // Конструктор // ----------------------------------------------------- public MenuItem(String label); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createMenuItem public void addNotify(); // Блокирование элемента меню public void disable(); // Разблокирование элемента меню public void enable(); // Блокирование или разблокирование элемента меню public void enable(boolean cond); // Получение текстовой строки меню public String getLabel(); // Проверка, является ли элемент меню заблокированным public boolean isEnabled(); // Получение строки параметров public String paramString(); // Установка текстовой строки для элемента меню public void setLabel(String label); } Пользуясь методами класса MenuItem вы можете блокировать или разблокировать отдельные строки меню, что нужно делать, например, если в данный момент функция, соответствующая строке меню, недоступна или не определена. Вы также можете изменять текстовые строки, соответствующие элементам меню, что может пригодиться для переопределения их назначения. Создание диалоговых панелей Диалоговые панели создаются на базе класса Dialog, краткое описание которого приведено ниже: public class java.awt.Dialog extends java.awt.Window { // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание диалоговой панели без заголовка public Dialog(Frame parent, boolean modal); // Создание диалоговой панели с заголовком public Dialog(Frame parent, String title, boolean modal); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Вызов метода createDialog public void addNotify(); // Получение строки заголовка диалоговой панели public String getTitle(); // Определение, является ли диалоговая панель модальной public boolean isModal(); // Определение возможности изменения размеров окна // диалоговой панели public boolean isResizable(); // Получение строки параметров protected String paramString(); // Включение или выключение возможности изменения // размеров окна диалоговой панели public void setResizable(boolean resizable); // Установка заголовка диалоговой панели public void setTitle(String title); } Для того чтобы создать свою диалоговую панель, вы должны определить новый класс, унаследовав его от класса Dialog, как это показано ниже: class MessageBox extends Dialog { . . . public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { super(parent, sTitle, modal); . . . resize(200, 100); . . . } } В этом классе нужно определить конструктор, который вызывает конструктор базового метода класса Dialog и определяет размеры окна диалоговой панели. Кроме того, в конструкторе вы должны создать все необходимые компоненты для размещения внутри диалоговой панели (кнопки, списки, текстовые поля, переключатели и так далее), а также выполнить размещение этих компонент, установив нужный режим размещения. Для окон класса Dialog устанавливается режим размещения BorderLayout. Если нужен другой режим размещения, необходимо установить его явным образом методом setLayout. Для отображения окна диалоговой панели необходимо вызвать метод show. Чтобы спрятать диалоговой окно, применяйте метод hide. Метод dispose удаляет окно диалоговой панели окончательно и освобождает все связанные с ним ресурсы. Когда пользователь пытается уничтожить окно диалоговой панели при помощи органов управления, расположенных в заголовке такого окна, возникает событие Event.WINDOW_DESTROY. Вы должны обработать его, обеспечив удаление окна диалоговой панели вызовом метода dispose, если, конечно, это соответствует логике работы вашей панели. Приложение FrameWnd В приложении FrameWnd мы демонстрируем создание окон, меню и диалоговых панелей на базе классов, описанных в этой главе. В окне аплета FrameWnd расположены две кнопки с названиями Show Frame Window и Hide Frame Window. Первая из них предназначена для отображения окна Main Frame Window, а вторая - для его временного удаления (скрытия). В окне Main Frame Window мы создали главное меню, содержащее меню File и Help. При выборе любой строки из этого меню, кроме строки Exit меню File, на экране появляется окно диалоговой панели Dialog from Frame с названием выбранной строки меню (рис. 8.2). Рис. 8.2. Окно и диалоговая панель, создаваемая аплетом FrameWnd Помимо меню, в окне Main Frame Window находится кнопка OK, нажатие на которую вызывает удаление окна. Кроме того, в нижней части окна отображается строка “Окно класса Frame”. В окне диалоговой панели, разделенном по вертикали на две части, находится текстовое поле для отображения сообщения и кнопка для завершения работы диалоговой панели. Обратите также внимание на то, что в самой нижней части окно Main Frame Window и Dialog from Frame находится предупреждающее сообщение “Warning: Applet Window”. Это предупреждение пользователю, что данное окно или диалоговая панель выведена не локальным приложением, запущенным на компьютере пользователя, а аплетом, загруженным из сети. Пользователь должен понимать, что данные, введенные им в окнах, созданных аплетами, передаются по сети и могут оказаться доступной кому угодно. Поэтому он не должен вводить конфиденциальную информацию, например, номера своих кредитных карточек. Исходные тексты приложения Исходный текст приложения FrameWnd приведен в листинге 8.1. Листинг 8.1. Файл FrameWnd\FrameWnd.java // ========================================================= // Работа с окнами и диалоговыми панелями // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // ========================================================= // Класс FrameWnd // Это наш аплет // ========================================================= public class FrameWnd extends Applet { // Окно, которое будет возникать из аплета MainFrameWnd fMainFrame; // Кнопка для отображения окна fMainFrame Button btnShowFrame; // Кнопка для удаления окна fMainFrame Button btnHideFrame; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: FrameWnd\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем новое окно на базе класса MainFrameWnd fMainFrame = new MainFrameWnd("Main Frame Window"); // Создаем кнопку для отображения этого окна btnShowFrame = new Button("Show Frame Window"); // Добавляем кнопку в окно аплета add(btnShowFrame); // Создаем кнопку для удаления окна fMainFrame btnHideFrame = new Button("Hide Frame Window"); // Добавляем кнопку в окно аплета add(btnHideFrame); } // ------------------------------------------------------- // destroy // Метод, получающий управление при завершении // работы аплета // ------------------------------------------------------- public void destroy() { // Удаляем окно fMainFrame и освобождаем все связанные // с ним ресурсы fMainFrame.dispose(); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Если нажата кнопка отображения окна fMainFrame, // показываем его с помощью метода show if(evt.target.equals(btnShowFrame)) { fMainFrame.show(); } // Если нажата кнопка удаления окна fMainFrame, // удаляем его с помощью метода hide else if(evt.target.equals(btnHideFrame)) { fMainFrame.hide(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else return false; return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } } // ========================================================= // Класс MainFrameWnd // На базе этого класса создается окно с меню // ========================================================= class MainFrameWnd extends Frame { // Кнопка, с помощью которой можно закрыть окно Button btnOK; // Главное меню окна MenuBar mbMainMenuBar; // Меню File Menu mnFile; // Меню Help Menu mnHelp; // ------------------------------------------------------- // MainFrameWnd // Конструктор класса // ------------------------------------------------------- public MainFrameWnd(String sTitle) { // Создаем окно, вызывая конструктор из базового класса super(sTitle); // Устанавливаем размеры окна resize(400, 200); // Устанавливаем цвет фона и изображения для окна setBackground(Color.yellow); setForeground(Color.black); // Устанавливаем режим добавления компонент FlowLayout setLayout(new FlowLayout()); // Создаем и добавляем в окно кнопку OK btnOK = new Button("OK"); add(btnOK); // Создаем главное меню mbMainMenuBar = new MenuBar(); // Создаем меню File mnFile = new Menu("File"); // Заполняем меню File mnFile.add("New"); // строка New mnFile.add("-"); // разделитель mnFile.add("Exit"); // строка Exit // Создаем меню Help mnHelp = new Menu("Help"); // Заполняем меню Help mnHelp.add("Content"); // строка Content mnHelp.add("-"); // разделитель mnHelp.add("About"); // строка About // Добавляем меню File и Help в главное // меню нашего окна mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp); // Устанавливаем для окна главное меню setMenuBar(mbMainMenuBar); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование // в созданном нами окне // ------------------------------------------------------- public void paint(Graphics g) { // Устанавливаем шрифт g.setFont(new Font("Helvetica", Font.PLAIN, 12)); // Рисуем строку g.drawString("Окно класса Frame", 10, 50); // Вызываем метод paint родительского класса super.paint(g); } // ------------------------------------------------------- // handleEvent // Обработка событий для окна // ------------------------------------------------------- public boolean handleEvent(Event evt) { // Если пользователь закрывает окно, // скрываем его с помощью метода hide if(evt.id == Event.WINDOW_DESTROY) { hide(); return true; } else return super.handleEvent(evt); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами в нашем окне // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Ссылка на элемент меню MenuItem mnItem; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Если пользователь нажал кнопку OK, скрываем окно if(evt.target.equals(btnOK)) { hide(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else return false; return true; } // Обработка событий от меню else if(evt.target instanceof MenuItem) { // Получам ссылку на элемент меню mnItem = (MenuItem)evt.target; // Если из меню File выбрана строка Exit, // завершаем работу виртуальной машины Java if(obj.equals("Exit")) { System.exit(0); } // Если из меню File выбрана строка New, // отображаем название строки в диалоговой панели else if(obj.equals("New")) { // Ссылка на диалоговую панель MessageBox mbox; // Создаем новую диалоговую панель mbox = new MessageBox("Item New selected", this, "Dialog from Frame", true); // Отображаем диалоговую панель mbox.show(); } // Если из меню File выбрана строка Content, // отображаем название строки в диалоговой панели else if(obj.equals("Content")) { MessageBox mbox; mbox = new MessageBox("Item Content selected", this, "Dialog from Frame", true); mbox.show(); } // Если из меню File выбрана строка About, // отображаем название строки в диалоговой панели else if(obj.equals("About")) { MessageBox mbox; mbox = new MessageBox("Item About selected", this, "Dialog from Frame", true); mbox.show(); } else return false; return true; } return false; } } // ========================================================= // Класс MessageBox // На базе этого класса мы создаем диалоговые панели // ========================================================= class MessageBox extends Dialog { // Поле для отображения текста сообщения Label lbMsg; // Кнопка для удаления диалоговой панели Button btnOK; // ------------------------------------------------------- // MessageBox // Конструктор класса // ------------------------------------------------------- public MessageBox(String sMsg, Frame parent, String sTitle, boolean modal) { // Создаем диалогвоую панель, вызывая конструктор // базового класса Dialog super(parent, sTitle, modal); // Устанавливаем размеры окна диалоговой панели resize(200, 100); // Устанавливаем режим размещения компонент GridLayout setLayout(new GridLayout(2, 1)); // Создаем и добавляем поле для отображения сообщения lbMsg = new Label(sMsg, Label.CENTER); add(lbMsg); // Создаем и добавляем кнопку для завершения работы // диалоговой панели btnOK = new Button("OK"); add(btnOK); } // ------------------------------------------------------- // handleEvent // Обработка событий для диалоговой панели // ------------------------------------------------------- public boolean handleEvent(Event evt) { // Если пользователь закрывает окно диалоговой панели, // скрываем его с помощью метода hide if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами в диалоговой панели // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Если нажата кнопка OK, удаляем диалоговую панель // и освобождаем все связанные с ней ресурсы if(evt.target.equals(btnOK)) { dispose(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else return false; return true; } return false; } } Исходный текст документа HTML, созданного для размещения аплета, приведен в листинге 8.2. Листинг 8.2. Файл FrameWnd\FrameWnd.html FrameWnd

The source. Описание исходного текста Рассмотрим по очереди поля и методы классов, определенных в нашем приложении. Поля класса FrameWnd В поле fMainFrame класса MainFrameWnd хранится ссылка на окно, которое будет создано, если пользователь нажмет кнопку “Show Frame Window”, расположенную в окне аплета. Класс MainFrameWnd создан нами на базе класса Frame. Поля с именами btnShowFrame и btnHideFrame предназначены, соответственно, для хранения ссылок на только что указанную кнопку и кнопку “Hide Frame Window”, с помощью которой можно скрыть окно. Метод getAppletInfo класса FrameWnd Этот метод возвращает информацию об аплете FrameWnd. Метод init класса FrameWnd В процессе инициализации аплета метод init создает объект класса MainFrameWnd - перекрывающееся окно с заголовком "Main Frame Window": fMainFrame = new MainFrameWnd("Main Frame Window"); Для этого вызывается конструктор из класса MainFrameWnd, созданного нами на базе класса Frame. После этого метод init создает две кнопки и добавляет их в окно аплета: btnShowFrame = new Button("Show Frame Window"); add(btnShowFrame); btnHideFrame = new Button("Hide Frame Window"); add(btnHideFrame); Первая из этих кнопок предназначена для отображения перекрывающегося окна, а вторая - для временного удаления или скрытия этого окна. Метод destroy класса FrameWnd При завершении работы аплета мы удаляем созданное нами окно и освобождаем все связанные с ним ресурсы, вызывая для окна метод dispose: fMainFrame.dispose(); Метод action класса FrameWnd Назначение метода action класса FrameWnd - обработка событий, вызванных кнопками кнопку “Show Frame Window” и “Hide Frame Window”, созданных в окне аплета: if(evt.target.equals(btnShowFrame)) fMainFrame.show(); else if(evt.target.equals(btnHideFrame)) fMainFrame.hide(); Если нажата кнопка “Show Frame Window”, для окна fMainFrame вызывается метод show, что приводит к появлению окна на экране. Если нажата кнопка “Hide Frame Window”, для этого окна вызывается метод hide, после чего окно исчезает с экрана. Исчезнувшее окно не уничтожается и вы можете снова отобразить его на экране, нажав кнопку “Show Frame Window”. Класс MainFrameWnd Класс MainFrameWnd предназначен для создания автономного перекрывающегося окна, которое существует вне окна навигатора. Этот класс был нами создан на базе класса Frame: class MainFrameWnd extends Frame { . . . } В классе мы определили несколько полей, конструктор для создания окна, метод paint для рисования в окне, метод handleEvent для обработки запроса на уничтожение окна, метод action для обработки события, вызванного кнопкой, расположенной в окне, а также выбором строк меню, созданного для окна. Поля класса MainFrameWnd В поле btnOK хранится ссылка на кнопку, при нажатии которой окно удаляется. Поле mbMainMenuBar класса MenuBar предназначено для хранения ссылки на главное меню окна. В него мы будем добавлять меню “File” и “Help”, идентификаторы которых хранятся в полях mnFile и mnHelp, соответственно. Конструктор класса MainFrameWnd В качестве единственного параметра нашему конструктору передается заголовок создаваемого окна. В первой исполняемой строке наш конструктор вызывает конструктор из базового класса, передавая ему строку заголовка через параметр: super(sTitle); Далее конструктор определяет размеры окна, вызывая для него метод resize: resize(400, 200); Затем мы устанавливаем для нашего окна желтый цвет фона и черный цвет изображения: setBackground(Color.yellow); setForeground(Color.black); По умолчанию для окон класса Frame устанавливается режим добавления компонент BorderLayout. Мы изменяем этот режим на FlowLayout, вызывая метод setLayout: setLayout(new FlowLayout()); Установив новый режим добавления компонент, мы располагаем в нашем окне кнопку, предварительно создав ее с помощью конструктора класса Button: btnOK = new Button("OK"); add(btnOK); Далее метод init приступает к формированию главного меню окна. Это меню создается как объект класса MenuBar: mbMainMenuBar = new MenuBar(); Затем мы создаем и наполняем меню “File”: mnFile = new Menu("File"); mnFile.add("New"); // строка New mnFile.add("-"); // разделитель mnFile.add("Exit"); // строка Exit Это меню создается на базе класса Menu. Обратите внимание, что между строками New и File расположен разделитель. Аналогичным образом мы добавляем в главное меню другое меню - “Help”: mnHelp = new Menu("Help"); mnHelp.add("Content"); // строка Content mnHelp.add("-"); // разделитель mnHelp.add("About"); // строка About После своего окончательного формирования меню “File” и “Help” добавляются в главное меню окна mbMainMenuBar: mbMainMenuBar.add(mnFile); mbMainMenuBar.add(mnHelp); И, наконец, когда главное меню будет сформировано, оно подключается к окну вызовом метода setMenuBar, как это показано ниже: setMenuBar(mbMainMenuBar); Метод paint класса MainFrameWnd Метод paint получает в качестве параметра ссылку на контекст отображения, пригодный для рисования в нашем окне. Пользуясь этим контекстом, мы устанавливаем шрифт текста и рисуем текстовую строку. Затем мы вызываем метод paint из базового класса Frame, на основе которого создан наш класс MainFrameWnd: g.setFont(new Font("Helvetica", Font.PLAIN, 12)); g.drawString("Окно класса Frame", 10, 50); super.paint(g); Метод handleEvent класса MainFrameWnd Для того чтобы определить реакцию окна на попытку пользователя закрыть окно с помощью органов управления, расположенных в заголовке окна, или другим способом, мы переопределили метод handleEvent. При получении кода события Event.WINDOW_DESTROY (удаление окна) мы просто скрываем окно, вызывая метод hide: if(evt.id == Event.WINDOW_DESTROY) { hide(); return true; } else return super.handleEvent(evt); Все другие события передаются для обработки методу handleEvent из базового класса. Метод action класса MainFrameWnd Этот метод обрабатывает события, связанные с кнопкой и меню. Если пользователь нажимает на кнопку OK, расположенную в окне, окно скрывается методом hide: if(evt.target.equals(btnOK)) { hide(); } Здесь для вас нет ничего нового. Рассмотрим более подробно процедуру обработки событий от меню. Вначале метод action проверяет, вызвано ли событие выбором строки из меню, сравнивая объект события с классом MenuItem: else if(evt.target instanceof MenuItem) { . . . } else return false; Если это так, в поле mnItem сохраняется ссылка на элемент меню, вызвавший событие: mnItem = (MenuItem)evt.target; Однако мы не используем этот элемент, так как для определения строки, выбранной пользователем, нам достаточно проанализировать второй параметр метода action: if(obj.equals("Exit")) System.exit(0); else if(obj.equals("New")) { MessageBox mbox; mbox = new MessageBox("Item New selected", this, "Dialog from Frame", true); mbox.show(); } else if(obj.equals("Content")) { . . . } else if(obj.equals("About")) { . . . } В данном случае второй параметр метода action будет представлять собой ссылку на строку, выбранную из меню, поэтому для определения выбранной строки мы можем выполнить простое сравнение методом equals. Если пользователь выбрал из меню File строку Exit, мы вызываем метод System. exit, предназначенный для завершения работы виртуальной машины Java. Таким способом вы можете завершить работу аплета, когда он выполняется в среде Microsoft Visual J++ в процессе отладки. Если же аплет запущен автономно в навигаторе, то завершения работы навигатора не произойдет. В том случае когда пользователь выбирает любую другую строку из меню, метод action создает диалоговую панель на базе определенного нами класса MessageBox. В этой диалоговой панели отображаетя название выбранной строки меню. Заметим, что сразу после создания конструктором диалоговая панель не появляется на экране. Мы отображаем ее, вызывая метод show. Класс MessageBox Для отображения названий выбранных строк меню мы создаем диалоговую панель, определив свой класс MessageBox на базе класса Dialog, как это показано ниже: class MessageBox extends Dialog { . . . } В классе MessageBox есть два поля, конструктор, методы handleEvent и action. Поля класса MessageBox Внутри диалоговой панели мы расположили текстовое поле класса Label, предназначенное для отображения сообщения, и кнопку с надписью OK, с помощью которой можно завершить работу диалоговой панели. Ссылка на текстовое поле хранится в поле lbMsg, на кнопку - в поле btnOK. Конструктор класса MessageBox Наш конструктор создает диалоговую панель с заданным сообщением внутри нее. Ссылка на строку сообщения передается конструктору через первый параметр. Остальные параметры используются конструктором базового класса Dialog для создания диалоговой панели: super(parent, sTitle, modal); После вызова конструктора из базового класса наш конструктор устанавливает размеры окна созданной диалоговой панели, вызывая метод resize: resize(200, 100); Отменяя установленный по умолчанию режим размещения компонент BorderLayout, конструктор устанавливает режим GridLayout: setLayout(new GridLayout(2, 1)); Окно диалоговой панели при этом разделяется на две части по горизонтали. В верхнюю часть добавляется текстовое поле для отображения сообщения, в нижнюю - кнопка OK: lbMsg = new Label(sMsg, Label.CENTER); add(lbMsg); btnOK = new Button("OK"); add(btnOK); Метод handleEvent класса MessageBox Когда пользователь пытается закрыть окно диалоговой панели, например, сделав двойной щелчок левой клавишей мыши по системному меню или одиночный щелчок по кнопке удаления окна, возникает событие Event.WINDOW_DESTROY. Мы его обрабатываем следующим образом: if(evt.id == Event.WINDOW_DESTROY) { dispose(); return true; } else return super.handleEvent(evt); Вызывая метод dispose, мы удаляем окно диалоговой панели и освобождаем все связанные с ним ресурсы. Метод action класса MessageBox Если пользователь нажимает кнопку OK, расположенную в окне диалоговой панели, метод action вызывает для панели метод dispose, удаляя эту панель с экрана и из памяти: if(evt.target.equals(btnOK)) { dispose(); } ЛИТЕРАТУРА 1. Фролов А.В., Фролов Г.В. Библиотека системного программиста. М.: ДИАЛОГ-МИФИ Т.11 - 13. Операционная система Microsoft Windows 3.1 для программиста, 1994 Т.14. Графический интерфейс GDI в Microsoft Windows, 1994 Т.15. Мультимедиа для Windows, 1994 Т.22. Операционная система Windows 95 для программиста, 1996 Т.23. Глобальные сети компьютеров. Практическое введение в Internet, E-Mail, FTP, WWW и HTML, программирование для Windows Sockets Т.29. Сервер Web своими руками. Язык HTML, приложения CGI и ISAPI, установка серверов Web для Windows, 1997 2. Д. Родли, Создание Java-апплетов: Пер. с англ., К: НИПФ “ДиаСофт Лтд.”, 1996 3. S. Davic, Learn Lava Now, Microsoft Press, One Microsoft Way, 1996 4. К. Джамса, Java: Пер. с англ., Мн.: ООО “Поппури”, 1996 5. Баженова И.Ю.,Язык программирования Java, М.: ДИАЛОГ-МИФИ, 1997 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ
, 27 , 19; 25; 27; 47 , 28; 47 Abstract Window Toolkit, 12 action, 73 ACTION_EVENT, 55 add, 116; 138; 139 addItem, 83; 88 addLayoutComponent, 108; 114 addPoint, 37 addSeparator, 139 ALIGN, 27 allowsMultipleSelections, 88 ALT, 27 ALT_MASK, 56 appendText, 102 Applet, 24; 72; 107 arg, 55 AWT, 12; 107 BOLD, 32 boolean, 9; 10 BorderLayout, 107; 111; 137 brighter, 32 Button, 71; 72 byte, 9 bytesWidth, 34 Canvas, 71 CardLayout, 108; 114 CENTER, 108 CGI, 150 char, 9 Character, 10 charsWidth, 34 charWidth, 34 CheckBox, 71; 77 CheckboxGroup, 78 Choice, 83 class, 14 clear, 88 clearRect, 29 clickCount, 55 clipRect, 29; 33; 39 CODE, 27 CODEBASE, 27 Color, 34 Color black, 31 Color blue, 31 Color cyan, 31 Color darkGray, 31 Color gray, 31 Color green, 31 Color lightGray, 31 Color magenta, 31 Color orange, 31 Color pink, 31 Color red, 31 Color white, 31 Color yellow, 31 Component, 72; 107 Container, 72; 107; 116; 136 Control Panel, 44 copyArea, 29; 39 countItems, 83; 88; 139 countMenus, 138 create, 29 CROSSHAIR_CURSOR, 136 CTRL_MASK, 56 darker, 32 DEFAULT_CURSOR, 136 DefWindowProc, 56 delete, 14 delItem, 88 deselect, 88 destroy, 25 Dialog, 136; 140 disable, 139 dispose, 29; 137 double, 9; 11 DOWN, 56 draw3DRect, 29 drawArc, 29; 38 drawBytes, 29 drawChars, 29 drawImage, 30 drawLine, 30; 35 drawOval, 30; 38 drawPolygon, 30; 36; 37 drawRect, 30; 35 drawRoundRect, 30; 36 drawString, 26; 30 E_RESIZE_CURSOR, 136 echoCharIsSet, 98 enable, 140 END, 56 equals, 32 Event, 55 Event.LIST_SELECT, 94 Event.WINDOW_DESTROY, 138; 141 evt, 55 extends, 24 F1-F12, 56 FileDialog, 136 fill3DRect, 30; 36 fillArc, 30 fillOval, 30; 38 fillPolygon, 30; 37 fillRect, 30; 35 fillRoundRect, 30; 36 final, 14 finalize, 30 first, 114 float, 9; 11 FlowLayout, 107; 108 FlowLayout.CENTER, 108 FlowLayout.LEFT, 108 FlowLayout.RIGHT, 108 Font, 32 Frame, 136 getAlignment, 95 getAppletInfo, 25 getAscent, 34 getBlue, 32 getBoundingBox, 37 getCheckboxGroup, 77 getClipRect, 30; 33 getColor, 30; 34 getColumns, 98; 102 getCurrent, 78 getCursorType, 137 getDescent, 34 getEchoChar, 98 getFamily, 32 getFont, 31; 32; 34 getFontMetrics, 31; 34 getGraphics, 117 getGreen, 32 getHeight, 34 getHelpMenu, 138 getHSBColor, 32 getIconImage, 137 getItem, 83; 139 getLabel, 72; 77; 140 getLeading, 34 getMaxAdvance, 34 getMaxAscent, 34 getMaxDescent, 34 getMenu, 138 getMenuBar, 137 getName, 33 getParameter, 47 getRed, 32 getRGB, 32 getRows, 88; 102 getSelectedIndex, 83 getSelectedIndexes, 88 getSelectedItem, 83 getSelectedItems, 88 getSelectedText, 99 getSelectionEnd, 99 getSelectionStart, 99 getSize, 33 getState, 77 getStyle, 33 getText, 95; 99 getTitle, 137; 140 getVisibleIndex, 88 getWidths, 34 GIF, 12 GOT_FOCUS, 55 Graphics, 26; 29; 35; 117 GridBagLayout, 107; 115 GridLayout, 107; 109 HAND_CURSOR, 136 handleEvent, 55; 73 hashCode, 33 HEIGHT, 27 hide, 138 HOME, 56 HSBtoRGB, 32 HSPACE, 27 http://www.dials.ccas.ru/frolov, 6 id, 55 IEEE 754, 10 init, 25 inline, 15 insertText, 102 inside, 37 int, 9 Integer, 10 IP, 11 ISAPI, 150 isBold, 33 isEditable, 99 isEnabled, 140 isItalic, 33 isModal, 140 isPlain, 33 isResizable, 137; 140 isSelected, 88 isTearOff, 139 ITALIC, 32 Java Applet Wizard, 16; 19 Java Development Kit, 3 Java Workspace, 16 java.applet, 12; 23; 24; 26; 28; 40; 45; 49; 59; 63; 67; 74; 79; 84; 90; 96; 100; 103; 109; 112; 118; 123; 132; 142 java.awt, 12; 24 java.awt.Checkbox, 77 java.awt.CheckboxGroup, 78 java.awt.Choice, 83 java.awt.Component, 26 java.awt.Container, 26 java.awt.Dialog, 140 java.awt.Graphics, 29 java.awt.image, 12 java.awt.List, 88 java.awt.Menu, 139 java.awt.MenuBar, 138 java.awt.MenuComponent, 138 java.awt.MenuContainer, 136; 138 java.awt.MenuItem, 139 java.awt.Panel, 26 java.awt.peer, 12 java.awt.TextArea, 102 java.awt.TextComponent, 99 java.awt.Window, 140 java.io, 11 java.lang, 10 java.lang.Object, 29 java.net, 11 java.util, 11 java.util.Vector, 65 JDK, 3 JIT, 3 Just-in-Time Compilation, 3 key, 55 KEY_ACTION, 55 KEY_ACTION_RELEASE, 55 KEY_PRESS, 55 KEY_RELEASE, 55 keyDown, 66 keyUp, 66 Label, 71; 94 last, 114 Layout Manager, 71; 107 layoutContainer, 108; 114 LEFT, 56; 108 List, 71; 87 LIST_DESELECT, 55 LIST_SELECT, 55 LOAD_FILE, 55 long, 9; 11 LOST_FOCUS, 55 makeVisible, 88 Math, 11 Menu Bar, 138 MenuBar, 138 MenuItem, 139 META_MASK, 56 MFC, 3 Microsoft Foundation Classes, 3 Microsoft Internet Explorer, 16 Microsoft Visual J++, 4 minimumLayoutSize, 108; 115 minimumSize, 88; 98; 102 MM_TEXT, 27 modifiers, 55 MOUSE_DOWN, 55 MOUSE_DRAG, 55 MOUSE_ENTER, 55 MOUSE_EXIT, 55 MOUSE_MOVE, 55 MOUSE_UP, 56 mouseDown, 57 mouseDrag, 57 mouseEnter, 57 mouseExit, 57 mouseMove, 57 mouseUp, 57 MOVE_CURSOR, 136 MS-DOS, 26 N_RESIZE_CURSOR, 136 NAME, 27; 47 NE_RESIZE_CURSOR, 136 Netscape Navigator, 16 new, 14 next, 114 NW_RESIZE_CURSOR, 136 Object, 14 out, 19 OWL, 10 paint, 25; 26 Panel, 107; 116; 117 PGDN, 56 PGUP, 56 PLAIN, 32 Polygon, 37 preferredLayoutSize, 108; 115 preferredSize, 89; 98; 102 previous, 114 println, 19 PrintStream, 19 public, 14 remove, 137; 138; 139 removeLayoutComponent, 108 replaceItem, 89 replaceText, 103 resize, 25 RGBtoHSB, 32 RIGHT, 56; 108 S_RESIZE_CURSOR, 136 SAVE_FILE, 56 SCROLL_ABSOLUTE, 56 SCROLL_LINE_DOWN, 56 SCROLL_LINE_UP, 56 SCROLL_PAGE_DOWN, 56 SCROLL_PAGE_UP, 56 Scrollbar, 71 SDK-Java, 4 SE_RESIZE_CURSOR, 136 select, 83; 89; 99 selectAll, 99 setAlignment, 95 setCheckboxGroup, 77 setColor, 31 setCurrent, 78 setCursor, 137 setEchoCharacter, 98 setEditable, 99 setFont, 31; 32 setHelpMenu, 138 setIconImage, 137 setLabel, 72; 77; 140 setLayout, 116 setMenuBar, 137 setMultipleSelections, 89 setPaintMode, 31; 33 setResizable, 137; 140 setState, 77 setText, 95; 99 setTitle, 137; 140 setXORMode, 31; 33 SHIFT_MASK, 56 short, 9 show, 115 showStatus, 57; 60; 61; 62; 75; 76; 80; 82; 92; 94; 110; 113; 119; 120; 121; 122 start, 25 static, 14 stop, 25 String, 11; 15 stringWidth, 34 super, 15 SW_RESIZE_CURSOR, 136 switch, 73 Symantec Cafe, 4 System, 19 target, 55 TCP, 11 TEXT_CURSOR, 136 TextArea, 71; 102 TextComponent, 71; 99 TextField, 71; 98 throws, 15 TITLE, 27 toString, 31; 32; 33 translate, 31; 33 UDP, 11 union, 14 UP, 56 URL, 11 VALUE, 47 W_RESIZE_CURSOR, 136 WAIT_CURSOR, 136 when, 55 WIDTH, 27 Window, 136 WINDOW_DEICONIFY, 56 WINDOW_DESTROY, 56 WINDOW_EXPOSE, 56 WINDOW_ICONIFY, 56 WINDOW_MOVED, 56 Windows Sockets, 4 WM_CHAR, 55 WM_LBUTTONDOWN, 55 WM_PAINT, 26 wrapper classes, 10 x, 55 y, 55 аплет, 19 аплеты, 3 атрибуты контекста отображения, 33 базовые типы данных, 9 библиотеки динамической загрузки DLL, 9 виртуальный процессор Java, 9 вложенные классы, 14 главное меню, 138 деструктор, 14 диалоговые панели, 136 замещающие классы, 10 интерфейсы, 15 контекст отображения, 26; 29 линии, 35 массив, 13 наследование, 15 область ограничения, 39 однострочное текстовое поле, 94 перезагрузка (переопределение) операторов, 11 перезагрузка операторов, 11 переопределение операторов, 15 прямоугольник, 35 размещение компонент в окне контейнера, 107 сборка мусора, 14 система Layout Manager, 107 система координат, 27 события, 55 создание классов, 14 списки, 83 список шрифтов, доступных аплету, 44 ссылки, 13 статические методы и поля, 14 указатели, 13 ОГЛАВЛЕНИЕ АННОТАЦИЯ ВВЕДЕНИЕ БЛАГОДАРНОСТИ КАК СВЯЗАТЬСЯ С АВТОРАМИ 1 НОВЫЙ ЯЗЫК ПРОГРАММИРОВАНИЯ Мобильность Java Базовые типы данных Библиотеки классов Java Встроенные классы Замещающие классы Класс String Другие встроенные классы Подключаемые библиотеки классов Библиотека классов java.util Библиотека классов java.io Библиотека классов java.net Библиотека классов java.awt Библиотека классов java.awt.image Библиотека классов java.awt.peer Библиотека классов java.applet Указатели, которых нет Массивы в Java Сборка мусора Особенности реализации классов в Java Определение класса Определение методов Переопределение операторов Интерфейсы Ссылки на методы класса Наследование 2 ПЕРВОЕ ПРИЛОЖЕНИЕ И ПЕРВЫЙ АПЛЕТ Приложение Hello Подготовка и запуск приложения Взгляд на исходный текст приложения Hello Простейший аплет Исходные файлы аплета HelloAp Файл HelloAp.java Конструктор HelloAp Метод getAppletInfo Метод init Метод destroy Метод start Метод stop Метод paint Файл HelloApp.html Упрощаем исходный текст аплета 3 РИСОВАНИЕ В ОКНЕ АПЛЕТА Контекст отображения Полотно для рисования Методы класса Graphics Установка атрибутов контекста отображения Выбор цвета Выбор шрифта Установка режима рисования Установка маски для рисования Сдвиг начала системы координат Определение атрибутов контекста отображения Определение границ области ограничения вывода Определение цвета, выбранного в контекст отображения Определение шрифта, выбранного в контекст отображения Определение метрик текущего шрифта Определение метрик заданного шрифта Рисование геометрических фигур Линии Прямоугольники и квадраты Многоугольники Овалы и круги Сегменты Задание области ограничения Копирование содержимого прямоугольной области Приложение Painter Исходные файлы приложения Painter Метод init Метод getAppletInfo Метод paint Приложение FontList Исходный текст приложения Описание исходного текста Метод init Метод paint Приложение TextOut Исходные тексты приложения TextOut Описание исходных текстов Поля класса TextOut Метод getParameterInfo Метод init Метод paint 4 ОБРАБОТКА СОБЫТИЙ Как обрабатываются события События от мыши Нажатие клавиши мыши Отпускание клавиши мыши Перемещение курсора мыши Выполнение операции Drag and Drop Вход курсора мыши в область окна аплета Выход курсора мыши из области окна аплета Приложение MouseClick Исходные тексты приложения Описание исходного текста Метод getAppletInfo Метод paint Метод mouseDown Методы mouseUp, mouseDrag, mouseEnter, mouseExit Метод mouseMove Приложение LineDraw Исходные тексты приложения Описание исходного текста Поля класса LineDraw Метод getAppletInfo Метод init Метод paint Метод mouseDown Метод mouseUp Метод mouseDrag Метод mouseMove События от клавиатуры Приложение KeyCode Исходные тексты приложения KeyCode Описание исходного текста Поля класса KeyCode Метод getAppletInfo Метод init Метод paint Метод keyDown Метод keyUp 5 КОМПОНЕНТЫ В ОКНЕ АПЛЕТА Кнопки Обработка событий от кнопки Приложение ButtonPress Исходные тексты приложения ButtonPress Описание исходного текста Поля класса ButtonPress Метод getAppletInfo Метод init Метод action Метод paint Переключатели Создание переключателей с независимой фиксацией Создание переключателей с зависимой фиксацией Приложение CheckBoxes Исходные тексты приложения CheckBoxes Описание исходного текста Поля класса CheckBoxes Метод getAppletInfo Метод init Метод action Метод paint Списки класса Choice Приложение ChoiceList Исходные тексты приложения ChoiceList Описание исходного текста Поля класса ChoiceList Метод getAppletInfo Метод init Метод action Метод paint Списки класса List Описание класса List Обработка событий от списка класса List Приложение ListBox Исходные тексты приложения Описание исходного текста Поля класса ListBox Метод getAppletInfo Метод init Метод action Метод handleEvent Метод paint Текстовое поле класса Label Приложение TextLabel Исходные тексты приложения Описание исходного текста Поля класса TextLabel Метод getAppletInfo Метод init Метод action Метод paint Текстовое поле класса TextField Приложение TxtField Исходные тексты приложения Описание исходного текста Поля класса TxtField Метод getAppletInfo Метод init Метод action Метод paint Многострочное текстовое поле класса TextArea Приложение TextEdit Исходные тексты приложения Описание исходного текста Поля класса TxtField Метод getAppletInfo Метод init Метод action Метод paint 6 НАСТРОЙКА СИСТЕМЫ LAYOUT MANAGER Режимы системы Layout Manager Режим FlowLayout Режим GridLayout Приложение Grid Исходные тексты приложения Описание исходного текста Режим BorderLayout Приложение Border Исходные тексты приложения Описание исходного текста Режим CardLayout Режим GridBagLayout 7 РАБОТА С ПАНЕЛЯМИ Создание панелей Добавление панелей Добавление компонент в панели Рисование в окне панели Приложение PanelDemo Исходные тексты приложения Описание исходного текста Поля класса PanelDemo Метод getAppletInfo Метод init Метод action Приложение Notebook Исходные тексты приложения Описание исходного текста Поля класса Notebook Метод getAppletInfo Метод init Метод action Метод paint Создание нового класса на базе класса Panel Приложение Panel2 Исходные тексты приложения Описание исходного текста Поля класса Panel2 Метод getAppletInfo класса Panel2 Метод init класса Panel2 Метод paint класса Panel2 Класс FirstPanel Метод paint класса FirstPanel Класс SecondPanel Метод paint класса SecondPanel 8 ОКНА И ДИАЛОГОВЫЕ ПАНЕЛИ Окна класса Frame Меню в окне класса Frame Классы Menu и MenuItem Создание диалоговых панелей Приложение FrameWnd Исходные тексты приложения Описание исходного текста Поля класса FrameWnd Метод getAppletInfo класса FrameWnd Метод init класса FrameWnd Метод destroy класса FrameWnd Метод action класса FrameWnd Класс MainFrameWnd Поля класса MainFrameWnd Конструктор класса MainFrameWnd Метод paint класса MainFrameWnd Метод handleEvent класса MainFrameWnd Метод action класса MainFrameWnd Класс MessageBox Поля класса MessageBox Конструктор класса MessageBox Метод handleEvent класса MessageBox Метод action класса MessageBox ЛИТЕРАТУРА ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ ОГЛАВЛЕНИЕ *------------- Создание приложений на языке Java Часть 2 АННОТАЦИЯ Книга представляет собой вторую часть практического пособия по созданию автономных приложений и аплетов, работающих под управлением навигаторов Internet, на языке программирования Java. Рассмотрены вопросы использования мультизадачности в приложениях Java, методы работы с графикой, звуком и анимацией. Много внимания уделено организации взаимодействия аплетов и сервера Web, а также описанию способов передачи данных и организации распределенной обработки информации в сети Internet с использованием приложений Java. Книга будет полезной всем, кто занимается разработкой приложений Java и оформлением страниц серверов Web. ВВЕДЕНИЕ В 30 томе “Библиотеки системного программиста”, который называется “Microsoft Visual J++. Создание приложений на языке Java. Часть 1” мы научили вас создавать аплеты Java и размещать их на страницах сервера Web. Была рассмотрена общая структура аплета, работа с контекстом отображения, органами управления, панелями, шрифтами и многое другое. Теперь настало время поговорить о более сложных и весьма полезных возможностях, которые открываются перед разработчиком приложений Java. Прежде всего, это мультизадачность. Практически любая современная операционная система, такая как Microsoft Windows, IBM OS/2 или UNIX, работает в мультизадачном режиме. Во многих случаях мультизадачность в целом благоприятно сказывается на производительности системы, так как во время ожидания одних задач свою работу могут выполнять другие задачи, готовые для этого. Например, если вы работаете в сети Internet, то можете одновременно подключиться к нескольким серверам FTP и Web, перекачивая сразу несколько файлов и загружая несколько документов HTML. При этом еще можно отправлять или получать электронную почту. Так как скорость поступления данных из сети Internet составляет в среднем 1 Кбайт в секунду, то даже при использовании модемного соединения общая скорость передачи данных в этом случае будет выше, чем при поочередной работе с серверами FTP, Web или с почтовым сервером. Пока один из серверов находится в состоянии ожидания, вы будете получать данные от другого сервера. Такое увеличение средней скорости передачи данных возможно из-за того, что при использовании протокола TCP/IP через общий канал могут одновременно передаваться пакеты данных, предназначенные для различных адресатов. Если вы создаете приложения Java, вам доступны удобные средства организации мультзадачности, в том числе средства синхронизации задач. Последнее необходимо для того чтобы параллельно работающие задачи корректно обращались с критическими ресурсами, требующими последовательного обращения. Заметим, что если вы собираетесь заниматься анимацией в окнах аплетов, вам в любом случае придется создавать мультизадачные приложения Java. Использование мультизадачности - единственный путь выполнения в приложениях Java любых периодических процедур, таких, например, как покадровое отображение мультфильма или медленный сдвиг текста для создания эффекта “бегущая строка”. Отдельная глава будет посвящена организации файлового ввода и вывода в приложениях Java. Хотя аплеты не имеют доступа к файлам, расположенным на дисках локального компьютера, самостоятельные приложения Java могут обращаться с файлами свободно. Аплеты также могут иметь доступ к файлам, расположенным в каталогах сервера Web. Много внимания в нашей книге мы уделим организации сетевого взаимодействия аплетов. Язык программирования Java был разработан специально для создания сетевых приложений, поэтому не удивительно, что в состав его библиотеки классов входят мощные средства, предназначенные для работы в сети. Мы, в частности, рассмотрим применение интерфейса потоковых и датаграмных сокетов. Вы сможете более полно реализовать возможности аплетов, если сумеете организовать взаимодействие аплетов и расширений сервера Web, таких как программы CGI или приложения ISAPI. В нашей книге вы найдете описание практических способов организации такого взаимодействия. Традиционно работа с растровыми графическими изображениями в приложениях Microsoft Windows или IBM OS/2 вызывала у программистов трудности, связанные с необходимостью разбора заголовков файлов графических изображений, реализации палитры и так далее. Библиотека классов Java содержит очень удобные и простые в использовании средства, избавляющие программистов от кошмарной работы с графическими файлами на низком уровне. Это особенно важно, так как аплеты часто применяются именно для усиления графического оформления страниц серверов Web. В нашей книге мы рассмотрим основные приемы работы с графическими изображениями. Отдельный раздел будет посвящена созданию анимационных изображений. Мы расскажем вам также и о том, как аплеты Java работают со звуковыми файлами. И хотя средства, предоставляемые библиотекой классов Java для, работы со звуком, нельзя назвать богатыми, вы все же сможете применить аплеты для озвучивания своих документов HTML. В отдельной главе нами будут рассмотрены приемы организации взаимодействия между несколькими аплетами, расположенными в одном документе HTML. Заключительная глава нашей книги посвящена созданию комбинированных приложений Java, которые могут работать и как самостоятельные приложения, и как аплеты, встроенные в документы HTML. БЛАГОДАРНОСТИ В работе над книгой нам помогал Максим Синев, который консультировал нас по различным вопросам . Мы выражаем благодарность генеральному директору АО “ДиалогНаука” Антимонову Сергею Григорьевичу и его заместителю Лященко Юрию Павловичу за возможность размещения информации о наших книгах на сервере Web по адресу http://www.dials. ccas.ru/frolov, а также за возможность доступа к сети Internet через сервер АО “ДиалогНаука”. Мы также благодарим корректора Кустова В. С. и сотрудников издательского отдела АО “Диалог-МИФИ” Голубева О. А., Голубева А. О., Дмитриеву Н. В., Виноградову Е. К., Кузьминову О. А. КАК СВЯЗАТЬСЯ С АВТОРАМИ Полную информацию о всех наших книгах серий “Библиотека системного программиста” и “Персональный компьютер. Шаг за шагом”, а также дискеты к книгам, статьи и другую информацию вы можете найти в сети Internet на серверах Web по следующим адресам: http://www.glasnet.ru/~frolov http://www.dials.ccas.ru/frolov Вы можете передать нам свои замечания и предложения по содержанию этой и других наших книг через электронную почту по адресам: [email protected] [email protected] Если электронная почта вам недоступна, присылайте ваши отзывы в АО “Диалог-МИФИ” по адресу: 115409, Москва, ул. Москворечье, 31, корп. 2, тел. 324-43-77 Приносим свои извинения за то что не можем ответить на каждое письмо. Мы также не занимаемся продажей и рассылкой книг, дискет, рекламы, отдельных фрагментов наших книг и исходных текстов к книгам. По этим вопросам обращайтесь непосредственно в издательство “Диалог-МИФИ”. 1 МУЛЬТИЗАДАЧНОСТЬ Наверное, сегодня уже нет необходимости объяснять, что такое мультизадачность. Все современные операционные системы, такие как Microsoft Windows 95, Microsoft Windows NT, IBM OS/2 или UNIX способны работать в мультизадачном режиме, повышая общую производительность системы за счет эффективного распараллеливания выполняемых задач. Пока одна задача находится в состоянии ожидания, например, завершения операции обмена данными с медленным периферийным устройством, другая может продолжать выполнять свою работу. Пользователи уже давно привыкли запускать параллельно несколько приложений для того чтобы делать несколько дел сразу. Пока одно из них занимается, например, печатью документа на принтере или приемом электронной почты из сети Internet, другое может пересчитывать электронную таблицу или выполнять другую полезную работу. При этом сами по себе запускаемые приложения могут работать в рамках одной задачи - операционная система сама заботится о распределении времени между всеми запущенными приложениями. Создавая приложения для операционной системы Microsoft Windows на языках программирования С или С++, вы могли решать многие задачи, такие как анимация или работа в сети, и без использования мультизадачности. Например, для анимации можно было обрабатывать сообщения соответствующим образом настроенного таймера. Приложениям Java такая методика недоступна, так как в этой среде не предусмотрено способов периодического вызова каких-либо процедур. Поэтому для решения многих задач вам просто не обойтись без мультизадачности. Процессы, задачи и приоритеты Прежде чем приступить к разговору о мультизадачности, следует уточнить некоторые термины. Обычно в любой мультизадачной операционной системе выделяют такие объекты, как процессы и задачи. Между ними существует большая разница, которую следует четко себе представлять. Процесс Процесс (process) - это объект, который создается операционной системой, когда пользователь запускает приложение. Процессу выделяется отдельное адресное пространство, причем это пространство физически недоступно для других процессов. Процесс может работать с файлами или с каналами связи локальной или глобальной сети. Когда вы запускаете текстовый процессор Microsoft Word for Windows или программу калькулятора, вы создаете новый процесс. Задача Для каждого процесса операционная система создает одну главную задачу (thread или task), которая является потоком выполняющихся по очереди команд центрального процессора. При необходимости главная задача может создавать другие задачи, пользуясь для этого программным интерфейсом операционной системы. Все задачи, созданные процессом, выполняются в адресном пространстве этого процесса и имеют доступ к ресурсам процесса. Однако задача одного процесса не имеет никакого доступа к ресурсам задачи другого процесса, так как они работают в разных адресных пространствах. При необходимости организации взаимодействия между процессами или задачами, принадлежащими разным процессам, следует пользоваться системными средствами, специально предназначенными для этого. Приоритеты задач в приложениях Java Если процесс создал несколько задач, то все они выполняются параллельно, причем время центрального процессора (или нескольких центральных процессоров в мультипроцессорных системах) распределяется между этими задачами. Распределением времени центрального процессора занимается специальный модуль операционной системы - планировщик. Планировщик по очереди передает управление отдельным задачам, так что даже в однопроцессорной системе создается полная иллюзия параллельной работы запущенных задач. Распределение времени выполняется по прерываниям системного таймера. Поэтому каждой задаче дается определенный интервал времени, в течении которого она находится в активном состоянии. Заметим, что распределение времени выполняется для задач, а не для процессов. Задачи, созданные разными процессами, конкурируют между собой за получение процессорного времени. Каким именно образом? Приложения Java могут указывать три значения для приоритетов задач. Это NORM_PRIORITY, MAX_PRIORITY и MIN_PRIORITY. По умолчанию вновь созданная задача имеет нормальный приоритет NORM_PRIORITY. Если остальные задачи в системе имеют тот же самый приоритет, то все задачи пользуются процессорным времени на равных правах. При необходимости вы можете повысить или понизить приоритет отдельных задач, определив для них значение приоритета, соответственно, MAX_PRIORITY или MIN_PRIORITY. Задачи с повышенным приоритетом выполняются в первую очередь, а с пониженным - только при отсутствии готовых к выполнению задач, имеющих нормальный или повышенный приоритет. Реализация мультизадачности в Java Для создания мультизадачных приложений Java вы должны воспользоваться классом java.lang.Thread. В этом классе определены все методы, необходимые для создания задач, управления их состоянием и синхронизации. Как пользоваться классом Thread? Есть две возможности. Во-первых, вы можете создать свой дочерний класс на базе класса Thread. При этом вы должны переопределить метод run. Ваша реализация этого метода будет работать в рамках отдельной задачи. Во-вторых, ваш класс может реализовать интерфейс Runnable. При этом в рамках вашего класса необходимо определить метод run, который будет работать как отдельная задача. Как вы скоро увидите, система автоматизированного создания приложений Java, входящая в состав Microsoft Visual J++, пользуется вторым из перечисленных выше способов. Этот способ удобен в тех случаях, когда ваш класс должен быть унаследован от какого-либо другого класса (например, от класса Applet) и при этом вам нужна мультизадачность. Так как в языке программирования Java нет множественного наследования, невозможно создать класс, для которого в качестве родительского будут выступать классы Applet и Thread. В этом случае реализация интерфейса Runnable является единственным способом решения задачи. Методы класса Thread В классе Thread определены три поля, несколько конструкторов и большое количество методов, предназначенных для работы с задачами. Ниже мы привели краткое описание полей, конструкторов и методов. public class java.lang.Thread extends java.lang.Object implements java.lang.Runnable { // ----------------------------------------------------- // Поля // ----------------------------------------------------- // Приоритеты задач public final static int NORM_PRIORITY; // нормальный public final static int MAX_PRIORITY; // максимальный public final static int MIN_PRIORITY; // минимальный // ----------------------------------------------------- // Конструкторы // ----------------------------------------------------- // Создание нового объекта Thread public Thread(); // Создвание нового объекта Thread с указанием объекта, // для которого будет вызываться метод run public Thread(Runnable target); // Аналогично предыдущему, но дополнительно задается // имя нового объекта Thread public Thread(Runnable target, String name); // Создание объекта Thread с указанием его имени public Thread(String name); // Создание нового объекта Thread с указанием группы // задачи и объекта, для которого вызывается метод run public Thread(ThreadGroup group, Runnable target); // Аналогично предыдущему, но дополнительно задается // имя нового объекта Thread public Thread(ThreadGroup group, Runnable target, String name); // Создание нового объекта Thread с указанием группы // задачи и имени объекта public Thread(ThreadGroup group, String name); // ----------------------------------------------------- // Методы // ----------------------------------------------------- // Текущее количество активных задач в группе, к которой // принадлежит задача public static int activeCount(); // Текущей задаче разрешается изменять объект Thread public void checkAccess(); // Определение количества фреймов в стеке public int countStackFrames(); // Определение текущей работающей задачи public static Thread currentThread(); // Принудительное завершение работы задачи public void destroy(); // Вывод текущего содержимого стека для отладки public static void dumpStack(); // Получение всех объектов Tread данной группы public static int enumerate(Thread tarray[]); // Определение имени задачи public final String getName(); // Определение текущего приоритета задачи public final int getPriority(); // Определение группы, к которой принадлежит задача public final ThreadGroup getThreadGroup(); // Прерывание задачи public void interrupt(); // Определение, является ли задача прерванной public static boolean interrupted(); // Определение, выполняется задача или нет public final boolean isAlive(); // Определение, является ли задача демоном public final boolean isDaemon(); // Определение, является ли задача прерванной public boolean isInterrupted(); // Ожидание завершения задачи public final void join(); // Ожидание завершения задачи в течение заданного времени. // Время задается в миллисекундах public final void join(long millis); // Ожидание завершения задачи в течение заданного времени. // Время задается в миллисекундах и наносекундах public final void join(long millis, int nanos); // Запуск временно приостановленной задачи public final void resume(); // Метод вызывается в том случае, если задача была // создана как объект с интерфейсом Runnable public void run(); // Установка для задачи режима демона public final void setDaemon(boolean on); // Устаовка имени задачи public final void setName(String name); // Установка приоритета задачи public final void setPriority(int newPriority); // Задержка задачи на заднное время. // Время задается в миллисекундах и наносекундах public static void sleep(long millis); // Задержка задачи на заднное время. // Время задается в миллисекундах и наносекундах public static void sleep(long millis, int nanos); // Запуск задачи на выполнение public void start(); // Остановка выполнения задачи public final void stop(); // Аварийная остановка выполнения задачи с // заданным исключением public final void stop(Throwable obj); // Приостановка задачи public final void suspend(); // Строка, представляющая объект-задачу public String toString(); // Приостановка текущей задачи для того чтобы // управление было передано другой задаче public static void yield(); } С помощью конструкторов вы можете создавать задачи различными способами, указывая при необходимости для них имя и группу. Имя предназначено для идентификации задачи и является необязательным атрибутом. Что же касается групп, то они предназначены для организации защиты задач друг от друга в рамках одного приложения. Подробнее мы расскажем об этом позже. Методы класса Thread предоставляют все необходимые возможности для управления задачами, в том числе для их синхронизации. Более подробное описание этих методов мы будем приводить по мере изложения материала. Создание дочернего класса на базе класса Thread Рассмотрим первый способ реализации мультизадачности, основанный на наследовании от класса Thread. При использовании этого способа вы определяете для задачи отдельный класс, например, так: class DrawRectangles extends Thread { . . . public void run() { . . . } } Здесь определен класс DrawRectangles, который является дочерним по отношению к классу Thread. Обратите внимание на метод run. Создавая свой класс на базе класса Thread, вы должны всегда определять этот метод, который и будет выполняться в рамках отдельной задачи. Заметим, что метод run не вызывается напрямую никакими другими методами. Он получает управление при запуске задачи методом start. Как это происходит? Рассмотрим процедуру запуска задачи на примере класса DrawRectangles. Вначале ваше приложение должно создать объект класса Thread: public class MultiTask2 extends Applet { Thread m_DrawRectThread = null; . . . public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } } } Создание объекта выполняется оператором new в методе start, который получает управление, когда пользователь открывает документ HTML с аплетом. Сразу после создания задача запускается на выполнение, для чего вызывается метод start. Что касается метода run, то если задача используется для выполнения какой либо периодической работы, то этот метод содержит внутри себя бесконечный цикл. Когда этот цикл завершается и метод run возвращает управление, задача прекращает свою работу нормальным, не аварийным образом. Для аварийного завершения задачи можно использовать метод interrupt. Остановка работающей задачи выполняется методом stop. Обычно остановка всех работающих задач, созданных аплетом, выполняется методом stop класса аплета: public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } } Напомним, что этот метод вызывается, когда пользователь покидает страницу сервера Web, содержащую аплет. Реализация интерфейса Runnable Описанный выше способ создания задач как объектов класса Thread или унаследованных от него классов кажется достаточнао естественным. Однако этот способ не единственный. Если вам нужно создать только одну задачу, работающую одновременно с кодом аплета, проще выбрать второй способ с использованием интерфейса Runnable. Идея заключается в том, что основной класс аплета, который является дочерним по отношению к классу Applet, дополнительно реализует интерфейс Runnable, как это показано ниже: public class MultiTask extends Applet implements Runnable { Thread m_MultiTask = null; . . . public void run() { . . . } public void start() { if (m_MultiTask == null) { m_MultiTask = new Thread(this); m_MultiTask.start(); } } public void stop() { if (m_MultiTask != null) { m_MultiTask.stop(); m_MultiTask = null; } } } Внутри класса необходимо определить метод run, который будет выполняться в рамках отдельной задачи. При этом можно считать, что код аплета и код метода run работают одновременно как разные задачи. Для создания задачи используется оператор new. Задача создается как объект класса Thread, причем конструктору передается ссылка на класс аплета: m_MultiTask = new Thread(this); При этом при запуске задачи управление получит метод run, определенный в классе аплета. Как запустить задачу? Запуск выполняется, как и раньше, методом start. Обычно задача запускается из метода start аплета, когда пользователь отображает страницу сервера Web, содержащую аплет. Остановка задачи выполняется методом stop. Применение мультизадачности для анимации Одно из наиболее распространенных применений аплетов - это создание анимационных эффектов типа бегущей строки, мерцающих огней или аналогичных, привлекающих внимание пользователя. Для того чтобы достичь такого эффекта, необходим какой либо механизм, позволяющий выполнять перерисовку всего окна аплета или его части периодически с заданным временным интервалом. Работа аплетов, так же как и обычных приложений операционной системы Microsoft Windows, основана на обработке событий. Для классического приложения Microsoft Windows событие - это приход сообщения в функцию окна. Основной класс аплета обрабатывает события, переопределяя те или иные методы базового класса Applet. Проблема с периодическим обновлением окна аплета возникает из-за того, что в языке Java не предусмотрено никакого механизма для создания генератора событий, способного вызывать какой-либо метод класса аплета с заданным интервалом времени. Вы не можете поступить так, как поступали в этой ситуации, разрабатывая обычные приложения Microsoft Windows - создать таймер и организовать обработку периодически поступающих от него сообщений WM_TIMER. Напомним, что перерисовка окна аплета выполняется методом paint, который вызывается виртуальной машиной Java асинхронно по отношению к выполнению другого кода аплета. Можно ли воспользоваться методом paint для периодической перерисовки окна аплета, организовав в нем, например, бесконечный цикл с задержкой? К сожалению, так поступать ни в коем случае нельзя. Метод paint после перерисовки окна аплета должен сразу возвратить управление, иначе работа аплета будет заблокирована. Единственный выход из создавшейся ситуации - создание задачи (или нескльких задач), которые будут выполнять рисование в окне аплета асинхронно по отношению к работе кода аплета. Например, вы можете создать задачу, которая периодически обновляет окно аплета, вызывая для этого метод repaint, или рисовать из задачи непосредственно в окне аплета, получив предварительно для этого окна контекст отображения. В примерах аплетов, приведенных в нашей книге, мы будем использовать оба способа. Приложение MultiTask Система автоматизированной разработки аплетов Microsoft Visual J++ позволяет указать, что создаваемый аплет будет мультизадачным. Для этого на третьем шаге в поле Would you like your applet to be multi-threaded следует включить переключатель Yes (рис. 1.1). Рис. 1.1. Добавление мультизадачности в создаваемый аплет Включив указанный переключатель, выключите пока переключатель Would you like support for animation - анимацией мы займемся немного позже. После завершения процедуры создания заготовки мультизадачного аплета выполните трансляцию и запустите аплет на выполнение. На экране появится текстовая строка с числом, значение которого быстро изменяется случайным образом. Исходные тексты приложения В листинге 1.1 представлены исходные тексты мультизадачного приложения MultiTask, созданного системой автоматизированной разработки аплетов, слегка измененного и снабженного нашими комментариями. В дальнейшем мы создадим блее сложные мультизадачные аплеты. Листинг 1.1. Файл MultiTask\MultiTask.java // ========================================================= // Периодическое обновление окна аплета // с использованием мультизадачности // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class MultiTask extends Applet implements Runnable { // Задача, которая будет обновлять окно аплета Thread m_MultiTask = null; // ------------------------------------------------------- // MultiTask // Конструктор класса MultiTask. Не используется // ------------------------------------------------------- public MultiTask() { } // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: MultiTask\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Author: Alexandr Frolov\r\n" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { } // ------------------------------------------------------- // destroy // Метод, получающий управление при // завершении работы аплета // ------------------------------------------------------- public void destroy() { } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Отображения строки со случайным числом g.drawString("Running: " + Math.random(), 10, 20); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { // Если задача еще не была создана, аплет создает // новую задачу как объект класса Thread, // а затем запускает ее if (m_MultiTask == null) { // Создание задачи m_MultiTask = new Thread(this); // Запуск задачи m_MultiTask.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда страница с аплетом // исчезает с экрана // ------------------------------------------------------- public void stop() { // Когда пользователь покидает страницу с аплетом, // метод stop останавливает задачу. // Остановка выполняется только в том случае, // если задача была создана if (m_MultiTask != null) { // Остановка задачи m_MultiTask.stop(); // Сброс ссылки на задачу m_MultiTask = null; } } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он вызывает периодическое обновление содержимого // окна аплета // ------------------------------------------------------- public void run() { // Выполняем обновление окна в бесконечном цикле while (true) { try { // Вызываем функцию обновления окна repaint(); // Выполняем небольшую задержку Thread.sleep(50); } catch (InterruptedException e) { // Если при выполнении задержки произошло // исключение, останавливаем работу задачи stop(); } } } } В листинге 1.2 представлен исходный текст документа HTML, предназначенный для совместной работы с нашим аплетом. Листинг 1.2. Файл MultiTask\MultiTask.html MultiTask

The source. Описание исходных текстов Для того чтобы аплет стал мультизадачным, его класс, который наследуется от класса Applet, дополнительно реализует интерфейс Runnable, как это показано ниже: public class MultiTask extends Applet implements Runnable { . . . } Внутри класса определяется поле с именем m_MultiTask типа Thread, которое предназначено для хранения ссылки на объект класса Thread, то есть на задачу: Thread m_MultiTask = null; Поле инициализируется значением null. Реальная ссылка на задачу будет сюда записана только после создания задачи. Рассмотрим теперь методы класса. Конструктор MultiTask В нашем аплете конструктор не используется. Метод getAppletInfo Метод getAppletInfo возвращает информацию об аплете. Метод init Метод init вызывается один раз при инициализации аплета. Наше приложение его не использует. Метод destroy При завершении работы аплета управление передается методу destroy. Мы его не используем. Метод paint Метод paint рисует в окне аплета текстовую строку и случайное число, полученное при помощи статического метода random класса Math: public void paint(Graphics g) { g.drawString("Running: " + Math.random(), 10, 20); } Напомним, что в однозадачном приложении метод paint вызывается при первом создании окна аплета, а также в случае необходимости перерисовки этого окна. В нашем аплете будет создана отдельная задача, выполняющая периодическую перерисовку окна при помощи метода repaint. Поэтому случайное число в окне аплета будет постоянно меняться. Метод start Метод start вызывается, когда пользователь отображает документ HTML, содержащий аплет. Наша реализация этого метода проверяет, создана ли задача перерисовки окна, и, если эта задача не запущена, создает и запускает ее: public void start() { if(m_MultiTask == null) { m_MultiTask = new Thread(this); m_MultiTask.start(); } } Первоначально в поле m_MultiTask находится значение null, поэтому при первом вызове метода start всегда создается задача как объекта класса Thread. При этом конструктору с помощью ключевого слова this передается ссылка на наш аплет, поэтому при запуске задачи управление будет передано методу run, определенному в аплете. Созданная задача не запускается автоматически. Для запуска необходимо вызвать метод start. Метод stop Когда пользователь покидает страницу с аплетом, имеет смысл остановить нашу задачу, чтобы она не отнимала ресурсов процессора. Остановка выполняется с помощью метода stop: public void stop() { if(m_MultiTask != null) { m_MultiTask.stop(); m_MultiTask = null; } } После остановки мы записываем в поле m_MultiTask значение null. Метод run Метод run получает управление при запуске задачи методом start. Если этот метод возвращает управление, соответствующая задача завершает свою работу. Наша реализация метода run состоит из бесконечного цикла, в котором периодически с задержкой 50 миллисекунд вызывается метод repaint: public void run() { while(true) { try { repaint(); Thread.sleep(50); } catch(InterruptedException e) { stop(); } } } Метод repaint вызывает принудительную перерисовку окна аплета, выполняемую методом paint. В нашем приложении этот метод отображает текстовую строку и случайное число. Для выполнения задержки метод run вызывает метод sleep из класса Thread. Так как метод sleep может вызывать исключение InterruptedException, мы его обрабатываем с помощью операторов try и catch. Если произошло исключение, мы завершаем задачу, вызывая метод stop. Заметим, что периодическая перерисовка окна аплета может привести к неприятному миганию, поэтому использованный здесь метод периодического обновления содержимого окна аплета нельзя назвать оптимальным. Позже мы рассмотрим другой метод, при котором такой перерисовки не происходит. Приложение Rectangles В предыдущем приложении задача выполняла периодическую перерисовку окна аплета, вызывая из метода run метод repaint. Такая методика приводит к сильному мерцанию окна аплета и поэтому во многих случаях нежелательна. Приложение Rectangles, постоянно отображающее в своем окне прямоугольники случайного размера, расположения и цвета (рис. 1.2), использует другой способ. Оно запускает задачу, которая рисует в окне аплета непосредственно. Рис. 1.2. Окно аплета Rectangles В результате в каждый момент времени перерисовывается только часть окна аплета и мерцание отсутствует. Исходные тексты приложения Исходный файл приложения Rectangles приведен в листинге 1.3. Листинг 1.3. Файл Rectangles\Rectangles.java // ========================================================= // Рисование прямоугольников в отдельной задаче // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; import java.util.*; public class Rectangles extends Applet implements Runnable { // Ссылка на задачу рисования прямоугольников Thread m_Rectangles = null; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Rectangles\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_Rectangles == null) { m_Rectangles = new Thread(this); m_Rectangles.start(); } } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void stop() { if (m_Rectangles != null) { m_Rectangles.stop(); m_Rectangles = null; } } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он рисует в окне аплета прямоугольники случайного // цвета, размера и расположения // ------------------------------------------------------- public void run() { // Получаем контекст отображения для окна аплета Graphics g = getGraphics(); // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); while (true) { int x, y, width, height; int rColor, gColor, bColor; // Выбираем случайным образом размеры // и расположение рисуемого прямоугольника x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; // Выбираем случайный цвет для // рисования прямоугольника rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); // Устанавливаем выбранный цвет // в контексте отображения g.setColor(new Color(rColor, gColor, bColor)); // Рисуем прямоугольник g.fillRect(x, y, width, height); // Выполняем задержку на 50 миллисекунд try { Thread.sleep(50); } catch (InterruptedException e) { stop(); } } } // ------------------------------------------------------- // mouseEnter // Метод вызывается, когда курсор мыши оказывается над // окном аплета // ------------------------------------------------------- public boolean mouseEnter(Event evt, int x, int y) { if (m_Rectangles != null) { // Когда курсор мыши оказывается над поверхностью // окна аплета, временно приостанавливаем // задачу рисования прямоугольников m_Rectangles.suspend(); } return true; } // ------------------------------------------------------- // mouseExit // Метод вызывается, когда курсор мыши покидает // окно аплета // ------------------------------------------------------- public boolean mouseExit(Event evt, int x, int y) { if (m_Rectangles != null) { // Когда курсор мыши покидает окно аплета, // возобновляем работу задачи рисования // прямоугольников m_Rectangles.resume(); } return true; } } В листинге 1.4 находится исходный текст документа HTML, созданного автоматически для нашего аплета. Листинг 1.4. Файл Rectangles\Rectangles.html Rectangles

The source. Описание исходных текстов Для создания задачи аплет Rectangles реализует интерфейс Runnable, то есть использует второй из описанных нами методов, как и предыдущий аплет. Ниже мы рассмотрим наиболее важные методы аплета Rectangles. Метод paint В предыдущем приложении метод paint периодически получал управление в результате периодического вызова метода repaint, выполняемого отдельной задачей. Метод paint аплета Rectangles вызывается только при инициализации и тогда, когда нужно обновить окно аплета. Этот метод определяет текущие размеры окна аплета, закрашивает окно желтым цветом и рисует вокруг окна черную рамку. Метод start Когда пользователь начинает просмотр документа HTML, содержащего наш аплет, метод start создает и запускает задачу. Для создания задачи мы используем оператор new, а для старта задачи - метод start класса Thread: public void start() { if (m_Rectangles == null) { m_Rectangles = new Thread(this); m_Rectangles.start(); } } Обратите внимание, что мы передаем конструктору класса Thread параметр this - ссылку на аплет. В результате роль задачи, работающей параллельно с кодом аплета, будет выполнять метод run, определенный в классе аплета. Ссылка на созданную задачу записывается в поле m_Rectangles. Метод stop Метод stop нашего аплета не имеет никаких особенностей. Он вызывается, когда пользователь покидает страницу сервера Web с аплетом. В этом случае метод останавливает задачу, вызывая для этого метод stop класса Thread: public void stop() { if (m_Rectangles != null) { m_Rectangles.stop(); m_Rectangles = null; } } После остановки в поле m_Rectangles записывается значение null. Это является признаком того, что задача остановлена. Метод run Программный код метода run работает в рамках отдельной задачи. Он рисует в окне аплета закрашенные прямоугольники. Прямоугольники имеют случайные координаты, расположение и цвет. Для того чтобы рисовать, необходимо получить контекст отображения. Так как наша задача, точнее, метод run определен в классе аплета, то он может получить контекст отображения, вызвав метод getGraphics: Graphics g = getGraphics(); Для рисования нам также нужно знать размеры окна аплета. Мы получаем эти размеры при помощи метода size: Dimension dimAppWndDimension = size(); Вооружившись контекстом отображения и размерами окна аплета, задача входит в бесконечный цикл рисования прямоугольников. В качестве генератора случайных чисел мы используем метод random из класса Math, который при каждом вызове возвращает новое случайное число типа double, лежащее в диапазоне значений от 0.0 до 1.0. Координаты по осям X и Y рисуемого прямоугольника определяются простым умножением случайного числа, полученного от метода random, соответственно, на ширину и высоту окна аплета: x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); Аналогично определяются размеры прямоугольника, однако чтобы прямоугольники не были слишком крупными, мы делим полученные значения на 2: width = (int)(dimAppWndDimension.width * Math.random())/2; height = (int)(dimAppWndDimension.height * Math.random())/2; Так как случайное число имеет тип double, в обоих случаях мы выполняем явное преобразование результата вычислений к типу int. Для случайного выбора цвета прямоугольника мы вычисляем отдельные цветовые компоненты, умножая значение, полученное от метода random, на число 255: rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); Полученные значения цветовых компонент используются в конструкторе Color для получения цвета. Этот цвет устанавливается в контексте отображения методом setColor: g.setColor(new Color(rColor, gColor, bColor)); Теперь все готово для рисования прямоугольника, которое мы выполняем при помощи метода fillRect: g.fillRect(x, y, width, height); После рисования прямоугольника метод run задерживает свою работу на 50 миллисекунд, вызывая метод sleep: try { Thread.sleep(50); } catch (InterruptedException e) { stop(); } Для обработки исключения InterruptedException, которое может возникнуть во время работы этого метода, мы предусмотрели блок try - catch. При возникновении указанного исключения работа задачи останавливается вызовом метода stop. Метод mouseEnter В предыдущем томе “Библиотеки системного программиста” мы рассказывали о методах mouseEnter и mouseExit. Первый из этих методов вызывается, когда в результате перемещения курсор мыши оказывается над окном аплета, а второе - когда курсор покидает окно аплета. Мы переопределили эти методы в своем аплете. Когда курсор мыши оказывается над окном аплета, мы временно приостанавливаем работу задачи, вызывая метод suspend: public boolean mouseEnter(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.suspend(); } return true; } Преостановленная задача не уничтожается. Ее работа может быть продолжена с помощью метода resume. Метод mouseExit Когда курсор мыши покидает окно аплета, вызывается метод mouseExit. Этот метод в нашем аплете возобновляет работу задачи, временно приостановленной методом suspend. Для этого используется метод resume, как это показано ниже: public boolean mouseExit(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.resume(); } return true; } Приложение MultiTask2 Поставим теперь перед собой другую цель - создать аплет, запускающий на выполнение сразу две задачи. Наше следующее приложение MultiTask2 запускает одну задачу для рисования прямоугольников, а другую - для рисования закрашенных эллипсов (рис. 1.3). Рис. 1.3. Окно аплета MultiTask2 Расположение, размеры и цвет прямоугольников и эллипсов выбирается случайным образом. Исходные тексты приложения Исходный текст приложения вы найдете в листинге 1.5. Листинг 1.5. Файл MultiTask2\ MultiTask2.java // ========================================================= // Рисование прямоугольников и эллипсов // в разных задачах // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // ========================================================= // Основной класс аплета // ========================================================= public class MultiTask2 extends Applet { // Ссылка на задачу рисования прямоугольников DrawRectangles m_DrawRectThread = null; // Ссылка на задачу рисования эллипсов DrawEllipse m_DrawEllipseThread = null; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: MultiTask2\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } if (m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this); m_DrawEllipseThread.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop(); m_DrawEllipseThread = null; } } } // ========================================================= // Класс задачи для рисования прямоугольников // ========================================================= class DrawRectangles extends Thread { // Контекст отображения окна аплета Graphics g; // Размеры окна аплета Dimension dimAppWndDimension; // ------------------------------------------------------- // DrawRectangles // Конструктор класса DrawRectangles // ------------------------------------------------------- public DrawRectangles(Applet Appl) { // Получаем и сохраняем контекст отображения g = Appl.getGraphics(); // Определяем текущие размеры окна аплета dimAppWndDimension = Appl.size(); } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он рисует в окне аплета прямоугольники случайного // цвета, размера и расположения // ------------------------------------------------------- public void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor; // Выбираем случайным образом размеры // и расположение рисуемого прямоугольника x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; // Выбираем случайный цвет для // рисования прямоугольника rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); // Устанавливаем выбранный цвет // в контексте отображения g.setColor(new Color(rColor, gColor, bColor)); // Рисуем прямоугольник g.fillRect(x, y, width, height); // Выполняем задержку на 50 миллисекунд try { Thread.sleep(50); } catch (InterruptedException e) { stop(); } } } } // ========================================================= // Класс задачи для рисования эллипсов // ========================================================= class DrawEllipse extends Thread { // Контекст отображения окна аплета Graphics g; // Размеры окна аплета Dimension dimAppWndDimension; // ------------------------------------------------------- // DrawEllipse // Конструктор класса DrawEllipse // ------------------------------------------------------- public DrawEllipse(Applet Appl) { g = Appl.getGraphics(); // Определяем текущие размеры окна аплета dimAppWndDimension = Appl.size(); } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он рисует в окне аплета эллипсы случайного // цвета, размера и расположения // ------------------------------------------------------- public void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor; // Выбираем случайным образом размеры // и расположение рисуемого эллипса x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; // Выбираем случайный цвет для рисования эллипса rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); // Устанавливаем выбранный цвет // в контексте отображения g.setColor(new Color(rColor, gColor, bColor)); // Рисуем эллипс g.fillOval(x, y, width, height); // Выполняем задержку на 50 миллисекунд try { Thread.sleep(50); } catch (InterruptedException e) { stop(); } } } } В листинге 1.6 мы привели исходный текст документа HTML, предназначенного для совместной работы с нашим приложением. Листинг 1.6. Файл MultiTask2\ MultiTask2.html MultiTask2

The source. Описание исходного текста В этом приложении мы создаем на базе класса Thread два класса, один из которых предназначен для создания задачи рисования прямоугольников, а другой - для создания задачи рисования закрашенных эллипсов. Что же касается основного класса аплета, то он унаследован, как обычно, от класса Applet и не реализует интерфейс Runnable. Поля класса MultiTask2 В классе MultiTask2 мы определили два поля с именами m_DrawRectThread и m_DrawEllipseThread: DrawRectangles m_DrawRectThread = null; DrawEllipse m_DrawEllipseThread = null; Эти поля являются ссылками на классы, соответственно DrawRectangles и DrawEllipse. Первый из них создан для рисования прямоугольников, а второй - эллипсов. Указанные поля инициализируются занчением null, что соответствует неработающим или несозданным задачам. Метод paint класса MultiTask2 Метод paint класса MultiTask2 не делает ничего нового по сравнению с аналогичным методом предыдущего аплета. Он просто раскрашивает окно аплета в желтый цвет и рисует вокруг него черную рамку. Метод start класса MultiTask2 Этот метод последовательно создает две задачи и запускает их на выполнение: public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } if (m_DrawEllipseThread == null) { m_DrawEllipseThread = new DrawEllipse(this); m_DrawEllipseThread.start(); } } Метод stop класса MultiTask2 Когда пользователь покидает страницу сервера Web с аплетом, метод stop класса MultiTask2 последовательно останавливает задачи рисования прямоугольников и эллипсов: public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop(); m_DrawEllipseThread = null; } } Поля класса DrawRectangles Класс DrawRectangles определен для задачи рисования прямоугольников. В поле g класа хранится контекст отображения окна аплета, а в поле dimAppWndDimension - размеры этого окна. Значения этих полей определяются конструктором класса по ссылке на главный класс аплета. Конструктор класса DrawRectangles В качестве параметра конструктору передается ссылка на класс аплета. Конструктор использует эту ссылку для получения и сохранения в полях класса контекста отображения и размеров окна аплета: g = Appl.getGraphics(); dimAppWndDimension = Appl.size(); Метод run класса DrawRectangles Код метода run выполняется в рамках отдельной задачи. Так как он аналогичен коду метода run предыдущего приложения, то для экономии места мы не будем его описывать. Класс DrawEllipse Исходный текст класса DrawEllipse лишь немного отличается от исходного текста класса DrawRectangles. Отличие есть в методе run - этот метод рисует не прямоугольники, а закрашенные эллипсы, вызывая для этого метод fillOval. Приложение Scroller Приложения, рассмотренные выше, демонстрируют различные методы реализации мультизадачности в Java, но едва ли вы найдете для них применение (разве лишь гипнотизирование пользователей). Ниже мы приведем исходные тексты приложения Scroller, которое имеет некоторую практическую ценность. В своем окне приложение Scroller показывает строки текста, медленно всплывающие вверх (рис. 1.4). Вы можете использовать этот аплет для размещения рекламной информации на своем сервере. Всплывающий текст (как и всякое движение на сранице сервера Web) будет привлекать внимание пользователя. Рис. 1.4. Окно аплета Scroller Строки (в количестве 6 штук) можно задавать в параметрах аплета, редактируя текст документа HTML, содержащего этот аплет. Первая строка выделяется красным цветом. Исходные тексты приложения Исходный текст приложения Scroller представлен в листинге 1.7. Листинг 1.7. Файл Scroller\Scroller.java // ========================================================= // Просмотр текста в режиме динамической свертки // по вертикали // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Scroller extends Applet implements Runnable { // Ссылка на задачу, выполняющую свертку Thread m_Scroller = null; // Отображаемые строки private String m_String1 = "Мы представляем наши новые книги"; private String m_String2 = "Том 29. Сервер Web своими руками"; private String m_String3 = "Том 30. Microsoft Visual J++. Создание приложений на языке Java. Часть 1"; private String m_String4 = "Том 31. Разработка приложений для Internet с Visual C++ и MFC"; private String m_String5 = "Том 32. Microsoft Visual J++. Создание приложений на языке Java. Часть 2"; private String m_String6 = ""; // Имена параметров private final String PARAM_String1 = "String1"; private final String PARAM_String2 = "String2"; private final String PARAM_String3 = "String3"; private final String PARAM_String4 = "String4"; private final String PARAM_String5 = "String5"; private final String PARAM_String6 = "String6"; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Scroller\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // getParameterInfo // Иинформация о параметрах // ------------------------------------------------------- public String[][] getParameterInfo() { String[][] info = { { PARAM_String1, "String", "Parameter description" }, { PARAM_String2, "String", "Parameter description" }, { PARAM_String3, "String", "Parameter description" }, { PARAM_String4, "String", "Parameter description" }, { PARAM_String5, "String", "Parameter description" }, { PARAM_String6, "String", "Parameter description" }, }; return info; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Рабочая строка String param; // Получение параметров param = getParameter(PARAM_String1); if (param != null) m_String1 = param; param = getParameter(PARAM_String2); if (param != null) m_String2 = param; param = getParameter(PARAM_String3); if (param != null) m_String3 = param; param = getParameter(PARAM_String4); if (param != null) m_String4 = param; param = getParameter(PARAM_String5); if (param != null) m_String5 = param; param = getParameter(PARAM_String6); if (param != null) m_String6 = param; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Определяем текущие размеры окна аплета dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_Scroller == null) { m_Scroller = new Thread(this); m_Scroller.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { if (m_Scroller != null) { m_Scroller.stop(); m_Scroller = null; } } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он выполняет динамическую свертку строк текста // ------------------------------------------------------- public void run() { // Счетчик сдвинутых строк int ShiftsCounter = 0; // Размер сдвига по вертикали int yShift; // Высота символов текста int yChar; // Номер текущей рисуемой строки int CurrentStr = 0; // Массив сдвигаемых строк String s[] = new String[6]; // Инициализация массива строк s[0] = m_String1; s[1] = m_String2; s[2] = m_String3; s[3] = m_String4; s[4] = m_String5; s[5] = m_String6; // Получаем контекст отображения Graphics g = getGraphics(); Dimension dimAppWndDimension = size(); // Определяем метрики текущего шрифта FontMetrics fm = g.getFontMetrics(); // Сохраняем полную высоту символов шрифта yChar = fm.getHeight(); // Бесконечный цикл сдвига строк while (true) { try { // Увеличиваем содержимое счетчика сдвигов ShiftsCounter++; // Если сдвинута полная строка, рисуем // следующую строку в нижней части окна if(ShiftsCounter == yChar + 5) { // Сбрасываем счетчик сдвигов ShiftsCounter = 0; // Первую строку отображаем красным цветом, // остальные - черным if(CurrentStr == 0) g.setColor(Color.red); else g.setColor(Color.black); // Рисуем строку g.drawString(s[CurrentStr], 10, dimAppWndDimension.height - 10); // Увеличиваем счетчик строк CurrentStr++; // Если уже нарисовали шесть строк, сбрасываем // счетчик строк if(CurrentStr > 5) CurrentStr = 0; } // Устанавливаем шаг сдвига равным одному пикселу yShift = 1; // Выполняем свертку g.copyArea(0, yShift + 1, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1, 0, -yShift); // Закрашиваем область ввода желтым цветом g.setColor(Color.yellow); g.fillRect(1, dimAppWndDimension.height - yShift - 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 1); // Выполняем задержку в 50 миллисекунд Thread.sleep(50); } catch (InterruptedException e) { stop(); } } } } В листинге 1.8 вы найдете исходный текст документа HTML, который был создан для аплета Scroller. Листинг 1.8. Файл Scroller\Scroller.html Scroller

The source. Описание исходных текстов Для выполнения плавного сдвига строк мы в нашем приложении создаем задачу, которая периодически рисует новые строки в нижней части окна, сдвигая перд этим старые строки вверх. Основной класс аплета реализует интерфейс Runnable, поэтому для задачи рисования и сдвига строк текста мы не создаем свой класс на базе класса Thread. Поля класса Scroller В поле m_Scroller записывается ссылка на задачу рисования и сдвига строк текста. Шесть полей типа String с именами от m_String1 до m_String6 предназначены для хранения сдвигаемых строк текста. Поля с именами от PARAM_String1 до PARAM_String6 хранят имена параметров аплета. Метод init Единственная задача метода init нашего приложения - получение параметров аплета и запись их в соотвестствующие поля класса. Эта задача решается с помощью метода getParameter, при этом строка param типа String используется как рабочая: String param; param = getParameter(PARAM_String1); if (param != null) m_String1 = param; Аналогичным образом метод получает значения всех шести параметров. Метод paint Метод paint подготавливает окно аплета для рисования - закрашивает его в желтый цвет и рисует вокруг окна черную рамку. Метод start Метод start основного класса аплета вызывается, когда пользователь отображает страницу сервера Web с аплетом. Наша реализация этого метода создает новую задачу и сохраняет ссылку на нее в поле m_Scroller. Метод stop Метод stop основного класса останавливает работу задачи, когда пользователь покидает страницу сервера Web с аплетом, вызывая для этого метод stop. Метод run Внутри метода run мы определили массив строк, проинициализировав его значениями, полученными из параметров аплета: String s[] = new String[6]; s[0] = m_String1; s[1] = m_String2; s[2] = m_String3; s[3] = m_String4; s[4] = m_String5; s[5] = m_String6; Задача, выполняющаяся в рамках метода run одновременно с кодом аплета, будет по очереди извлекать строки из этого массива и отображать их в нижней части окна аплета. Так как для рисования строк текста нужно знать контекст отображения, мы получаем его при помощи метода getGraphics: Graphics g = getGraphics(); Мы также определяем размеры окна аплета, знание которых необходимо для организации сдвига содержимого окна: Dimension dimAppWndDimension = size(); Перед тем как запустить бесконечный цикл, мы также определяем метрики текущего шрифта и высоту символов шрифта: FontMetrics fm = g.getFontMetrics(); yChar = fm.getHeight(); В рамках бесконечного цикла мы подсчитываем количество сдвигов (в счетчике ShiftsCounter), а также сдвинутые строки (в счетчике CurrentStr). Заметим, что для обеспечения плавности сдвига мы перемещаем строки по одному пикселу. Когда величина сдвига достигает высоты символов yChar плюс 5, метод run рисует новую строку. Перед рисованием строки мы выбираем в контекст отображения красный или черный цвет, в зависимости от номера строки: if(CurrentStr == 0) g.setColor(Color.red); else g.setColor(Color.black); Вы можете выделять нужные вам строки любым другим способом, например, наклоном или жирным шрифтом. Для рисования строки мы вызываем метод drawString: g.drawString(s[CurrentStr], 10, dimAppWndDimension.height - 10); Строка будет нарисована на десять пикселов выше нижней границы окна аплета. После рисования строки мы проверяем, последняя она, или нет: CurrentStr++; if(CurrentStr > 5) CurrentStr = 0; Если строка последняя, мы сбрасываем счетчик текущей строки, после чего перебор строк начнется с самого начала. Для выполнения свертки мы вызываем метод copyArea, знакомый вам по 30 тому “Библиотеки системного программиста”: yShift = 1; g.copyArea(0, yShift + 1, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1, 0, -yShift); Этот метод сдвигает содержимое прямоугольной области экрана, заданной первыми четырьмя параметрами. Величина сдвига определяются двумя последними параметрами метода. В нашем случае сдвиг выполняется по вертикальной оси на значение -1, то есть на один пиксел вверх. После сдвига освободившаяся область закрашивается желтым цветом: g.setColor(Color.yellow); g.fillRect(1, dimAppWndDimension.height - yShift - 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 1); Далее выполняется задержка на 50 миллисекунд, после чего работа бесконечного цикла возобновляется с самого начала: Thread.sleep(50); Приложение HorzScroll Практически в каждой книге, посвященной программированию на языке Java, вы найдете исходные тексты приложения, выполняющие горизонтальную прокрутку строки текста. Эффект бегущей строки достаточно широко используется для привлечения внимания пользователя. Реализация эффекта бегущей его достаточно проста. Аплет создает задачу, которая периодически перерисовывает окно, вызывая метод repaint. Метод paint отображает строку в окне, каждый раз изменяя ее начальные координаты для получения эффекта сдвига. Мы уверены, что вы сами легко справитесь с задачей создания бегущей строки, поэтому предложим вам кое-что иное. А именно, мы подготовили приложение, которое выписывает текстовую строку в своем окне последовательно буква за буквой. Когда вся строка будет нарисована, окно очищается и затем процесс возобновляется вновь (рис. 1.5). Рис. 1.5. Окно аплета HorzScroll В чем сложность создания такого аплета? Главным образом в том, что все символы пропорционального шрифта имеют разную ширину, и вам нужно правильно вычислять координаты каждого нового символа. Исходные тексты приложения Исходные тексты приложения HorzScroll приведены в листинге 1.9. Листинг 1.9. Файл HorzScroll\HorzScroll.java // ========================================================= // Рисование строки текста по буквам // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class HorzScroll extends Applet implements Runnable { // Ссылка на задачу рисования строки текста Thread m_HorzScroll = null; // Значения параметров по умолчанию private String m_Str = "Hello from Java!"; private String m_Fnt = "Helvetica"; private String m_style = "BOLD"; private int m_size = 12; private String m_color = "red"; private int m_delay = 50; // Имена параметров private final String PARAM_str = "str"; private final String PARAM_font = "font"; private final String PARAM_style = "style"; private final String PARAM_size = "size"; private final String PARAM_color = "color"; private final String PARAM_delay = "delay"; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: HorzScroll\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // getParameterInfo // Иинформация о параметрах // ------------------------------------------------------- public String[][] getParameterInfo() { String[][] info = { { PARAM_str, "String", "String to draw" }, { PARAM_font, "String", "Font name" }, { PARAM_style, "String", "Font style" }, { PARAM_size, "String", "Font size" }, { PARAM_color, "String", "String color" }, { PARAM_delay, "int", "Output delay" }, }; return info; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { // Рабочая строка String param; // Получение значений параметров param = getParameter(PARAM_str); if (param != null) m_Str = param; param = getParameter(PARAM_font); if (param != null) m_Fnt = param; param = getParameter(PARAM_style); if (param != null) m_style = param; param = getParameter(PARAM_size); if (param != null) m_size = Integer.parseInt(param); param = getParameter(PARAM_color); if (param != null) m_color = param; param = getParameter(PARAM_delay); if (param != null) m_delay = Integer.parseInt(param); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Определяем текущие размеры окна аплета dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_HorzScroll == null) { m_HorzScroll = new Thread(this); m_HorzScroll.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { if (m_HorzScroll != null) { m_HorzScroll.stop(); m_HorzScroll = null; } } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он выполняет рисование текстовой строки по буквам // ------------------------------------------------------- public void run() { // Получаем контекст отображения Graphics g = getGraphics(); // Выбираем шрифт в контекст отображения if(m_style.equals("BOLD")) g.setFont(new Font(m_Fnt, Font.BOLD, m_size)); else if(m_style.equals("ITALIC")) g.setFont(new Font(m_Fnt, Font.ITALIC, m_size)); else g.setFont(new Font(m_Fnt, Font.PLAIN, m_size)); // Выбираем цвет в контекст отображения if(m_color.equals("red")) g.setColor(Color.red); else if(m_color.equals("green")) g.setColor(Color.green); else g.setColor(Color.black); // Определяем размеры окна аплета Dimension dimAppWndDimension = size(); // Определяем метрики текущего шрифта FontMetrics fm = g.getFontMetrics(); // Номер текущего рисуемого символа строки int nCurrentChar = 0; // Позиция для рисования по вертикали int yPos = fm.getHeight() + 5; // Текущая позиция рисования символа по горизонтали int nCurrentXPos = 10; // Ширина текущего символа в пикселах int nCurrentCharWidth; // Бесконечный цикл рисования while (true) { try { try { // Определяем ширину текущего символа nCurrentCharWidth = fm.charWidth(m_Str.charAt(nCurrentChar)); // Массив для преобразования кода символа в сивол char[] ch; // Временная строка String s; // Создаем массив из одного элемента ch = new char[1]; // Записыаем в него код нажатой клавиши ch[0] = m_Str.charAt(nCurrentChar); // Преобразуем в строку s = new String(ch); // Рисуем текущий символ в текущей позиции g.drawString(s, nCurrentXPos, yPos); // Увеличиваем текущую позицию на ширину // нарисованного символа nCurrentXPos += nCurrentCharWidth; // Переходим к следующему символу в строке nCurrentChar++; } // Обработка выхода за пределы строки catch (StringIndexOutOfBoundsException e) { // Сбрасываем номер текущего символа и // текущую позицию nCurrentChar = 0; nCurrentXPos = 10; // Перерисовываем окно аплета repaint(); // Задержка после перерисовки окна try { Thread.sleep(500); } catch (InterruptedException ee) { stop(); } } // Задержка между рисованием отдельных символов Thread.sleep(m_delay); } catch (InterruptedException e) { stop(); } } } } В листинге 1.10 приведен исходный текст документа HTML, созданного для аплета HorzScroll. Листинг 1.10. Файл HorzScroll\HorzScroll.html HorzScroll

The source. Описание исходных текстов Аплет создает задачу отображения символов текстовой строки, для чего он реализует интерфейс Runnable (как и предыдущий аплет). Эта задача рисует отдельные символы текстовой строки, каждый раз определяя их ширину. Поля класса HorzScroll В поле m_HorzScroll хранится сслыка на задачу рсиования строки. В документе HTML нашему аплету передается несколько параметров, задающих отображаемую строку и определяющих ее внешний вид. В поле m_Str хранится отображаемая строка. Шритф, стилевое оформление и размер смиволов строки хранится, соответственно, в полях m_Fnt, m_style и m_size. Цвет символов строки одинаковый и хранится в поле m_color. Поле m_delay хранит текущее время задержки между отображением отдельных символов строки в миллисекундах. Метод init Метод init получает текущие значения параметров аплета и сохраняет их в соответствующих полях основного класса. Способ получения параметров аналогичен использованному в предыдущем приложении. Метод paint Метод paint просто закрашивает окно аплета желтым цветом и затем обводит его черной рамкой. Метод run Перед запуском бесконечного цикла отображения символов строки метод run получает контекст отображения и устанавливает в нем параметры шрифта в соответствии со значениями, переданными аплету через документ HTML. Прежде всего, метод run получает контекст отображения: Graphics g = getGraphics(); Затем в этом контексте отображения устанавливается шрифт с жирным, наклонным или обычным начертанием: if(m_style.equals("BOLD")) g.setFont(new Font(m_Fnt, Font.BOLD, m_size)); else if(m_style.equals("ITALIC")) g.setFont(new Font(m_Fnt, Font.ITALIC, m_size)); else g.setFont(new Font(m_Fnt, Font.PLAIN, m_size)); Обратите внимание, что название шрифта передается конструктору класса Font через первый параметр, а размер символов - через последний. В зависимости от содержимого поля m_color метод run устанавливает один из трех цветов для отображения символов текстовой строки: if(m_color.equals("red")) g.setColor(Color.red); else if(m_color.equals("green")) g.setColor(Color.green); else g.setColor(Color.black); Помимо этого, до запуска цикла метод run получает размеры окна аплета и метрики шрифта, только что установленного в контексте отображения: Dimension dimAppWndDimension = size(); FontMetrics fm = g.getFontMetrics(); В переменную nCurrentChar, хранящую номер текущего отображаемого символа, записывается нулевое значение. Кроме того, вычисляется позиция для рисования строки по вертикальной оси yPos и устанавливается начальная позиция первого символа строки по горизонтальной оси nCurrentXPos: int yPos = fm.getHeight() + 5; int nCurrentXPos = 10; Далее метод run запускает бесконечный цикл рисования символов. Первое, что метод run делает в этом цикле, это вычисление ширины текущего символа, сохраняя ее в переменной nCurrentCharWidth: nCurrentCharWidth = fm.charWidth(m_Str.charAt(nCurrentChar)); Текущий символ извлекается из строки при помощи метода charAt, определенном в классе String. Ширина извлеченного таким образом символа символа определяется методом charWidth из класса метрик шрифта FontMetrics. Далее нам нужно отобразить текущий символ в заданной позиции, для чего мы воспользуемся методом drawString. С этим, однако, есть небольшая сложность - метод drawString не может отображать отдельные символы. Поэтому мы должны создать строку, состоящую из одного символа и передать эту строку методу drawString. Эта задача решается достаточно просто. Прежде всего, мы определяем массив, состоящий из одного символа, и временную текстовую строку s: char[] ch; String s; ch = new char[1]; В самый первый элемент массива мы записываем текущий символ: ch[0] = m_Str.charAt(nCurrentChar); Затем мы создаем строку из массива, пользуясь конструктором, специально предусмотренным для этой цели в классе String: s = new String(ch); Все! Теперь можно отображать строку, состоящую из одного символа, в текущей позиции: g.drawString(s, nCurrentXPos, yPos); После отображения символа мы увеличиваем текущую позицию по горизонтальной оси на ширину только что нарисованного символа, а затем переходим к следующему символу в строке, увеличивая счетчик nCurrentChar: nCurrentXPos += nCurrentCharWidth; nCurrentChar++; По идее после увеличения счетчика nCurrentChar мы должны были бы проверить его значение на предмет выхода за границы строки, однако метод run поступает лучше. При получении из строки символа с номером nCurrentChar при помощи метода charAt возможно возникновение исключения StringIndexOutOfBoundsException, которое и обрабатывается методом run. При обработке исключения метод run сбрасывает номер текущего символа и текущую позицию, перерисовывает окно аплета (в результате чего оно очищется), и после выполнения задержки в полсекунды продолжает процедуру рисования символов. Синхронизация задач Мультизадачный режим работы открывает новые возможности для программистов, однако за эти возможности приходится расплачиваться усложнением процесса проектирования приложения и отладки. Основная трудность, с которой сталкиваются программисты, никогда не создававшие ранее мультизадачные приложения, это синхронизация одновременно работающих задач. Для чего и когда она нужна? Однозадачная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однозадачной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и задачи, работающие одновременно в мультизадачной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений. Поясним это на простом примере. Пусть мы создаем программу, выполняющую операции с банковским счетом. Операция снятия некоторой суммы денег со счета может происходить в следующей последовательности: * на первом шаге проверяется общая сумма денег, которая хранится на счете; * если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину; * значение остатка записывается на текущий счет. Если операция уменьшения текущего счета выполняется в однозадачной системе, то никаких проблем не возникнет. Однако представим себе, что два процесса пытаются одновременно выполнить только что описанную операцию с одним и тем же счетом. Пусть при этом на счету находится 5 млн. долларов, а оба процесса пытаются снять с него по 3 млн. долларов. Допустим, события разворачиваются следующим образом: * первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов; * второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов; * первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет; * второй процесс выполняет ту же самую операцию, так как после проверки считает, что на счету по-прежнему хранится 5 млн. долларов. В результате получилось, что со счета, на котором находилось 5 млн. долларов, было снято 6 млн. долларов, и при этом там осталось еще 2 млн. долларов! Итого - банку нанесен ущерб в 3 млн. долларов. Как же составить программу уменьшения счета, чтобы она не позволяла вытворять подобное? Очень просто - на время выполнения операций над счетом одним процессом необходимо запретить доступ к этому счету со стороны других процессов. В этом случае сценарий работы программы должен быть следующим: * процесс блокирует счет для выполнения операций другими процессами, получая его в монопольное владение; * процесс проводит процедуру уменьшения счета и записывает на текущий счет новое значение остатка; * процесс разблокирует счет, разрешая другим процессам выполнение операций. Когда первый процесс блокирует счет, он становится недоступен другим процессам. Если второй процесс также попытается заблокировать этот же счет, он будет переведен в состояние ожидания. Когда первый процесс уменьшит счет и на нем останется 2 млн. долларов, второй процесс будет разблокирован. Он проверит остаток, убедится, что сумма недостаточна и не будет проводить операцию. Таким образом, в мультизадачной среде необходима синхронизация задач при обращении к критическим ресурсам. Если над такими ресурсами будут выполняться операции в неправильной последовательности, это приведет к возникновению трудно обнаруживаемых ошибок. В языке программирования Java предусмотрено несколько средств для синхронизации задач, которые мы сейчас рассмотрим. Синхронизация методов Возможность синхронизации как бы встроена в каждый объект, создаваемый приложением Java. Для этого объекты снабжаются защелками, которые могут быть использованы для блокировки задач, обращающихся к этим объектам. Чтобы воспользоваться защелками, вы можете объявить соответствующий метод как synchronized, сделав его синхронизированным: public synchronized void decrement() { . . . } При вызове синхронизированного метода соответствующий ему объект (в котором он определен) блокируется для использования другими синхронизированными методами. В результате предотвращается одновременная запись двумя методами значений в область памяти, принадлежащую данному объекту. Использование синхронизированных методов - достаточно простой способ синхронизации задач, обращающихся к общим критическим ресурсам, наподобие описанного выше банковского счета. Заметим, что не обязательно синхронизовать весь метод - можно выполнить синхронизацию только критичного фрагмента кода. . . . synchronized(Account) { if(Account.check(3000000)) Account.decrement(3000000); } . . . Здесь синхронизация выполняется для объекта Account. Блокировка задачи Синхронизированная задача, определенная как метод типа synchronized, может переходить в заблокированное состояние автоматически при попытке обращения к ресурсу, занятому другим синхронизированным методом, либо при выполнении некоторых операций ввода или вывода. Однако в ряде случаев полезно иметь более тонкие средства синхронизации, допускающие явное использование по запросу приложения. Блокировка на заданный период времени С помощью метода sleep можно заблокировать задачу на заданный период времени. Мы уже пользовались этим методом в предыдущих приложениях, вызывая его в цикле метода run: try { Thread.sleep(500); } catch (InterruptedException ee) { . . . } В данном примере работа задачи Thread приостанавливается на 500 миллисекунд. Заметим, что во время ожидания приостановленная задача не отнимает ресурсы процессора. Так как метод sleep может создавать исключение InterruptedException, необходимо предусмотреть его обработку. Для этого мы использовали операторы try и catch. Временная приостановка и возобновление работы Методы suspend и resume позволяют, соответственно, временно приостанавливать и возобновлять работу задачи. Мы уже пользовались этими методами в приложении Rectangles для приостановки и возобновления работы задачи рисования прямоугольников. Задача приостанавливалась, когда курсор мыши оказывался над окном аплета: public boolean mouseEnter(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.suspend(); } return true; } Работа задачи возобновлялась, когда курсор мыши покидал окно аплета: public boolean mouseExit(Event evt, int x, int y) { if (m_Rectangles != null) { m_Rectangles.resume(); } return true; } Ожидание извещения Если вам нужно организовать взаимодействие задач таким образом, чтобы одна задача управляла работой другой или других задач, вы можете воспользоваться методами wait, notify и notifyAll, определенными в классе Object. Метод wait может использоваться либо с параметром, либо без параметра. Этот метод переводит задачу в состояние ожидания, в котором она будет находиться до тех пор, пока для задачи не будет вызван извещающий метод notify, notifyAll, или пока не истечет период времени, указанный в параметре метода wait. Как пользоваться методами wait, notify и notifyAll? Метод, который будет переводиться в состояние ожидания, должен быть синхронизированным, то есть его следует описать как synchronized: public synchronized void run() { while (true) { . . . try { Thread.wait(); } catch (InterruptedException e) { } } } В этом примере внутри метода run определен цикл, вызывающий метод wait без параметров. Каждый раз при очередном проходе цикла метод run переводится в состояние ожидания до тех пор, пока другая задача не выполнит извещение с помощью метода notify. Ниже мы привели пример задачи, вызывающией метод notify: public void run() { while (true) { try { Thread.sleep(30); } catch (InterruptedException e) { } synchronized(STask) { STask.notify(); } } } Эта задача реализована в рамках отдельного класса, конструктору которого передается ссылка на задачу, вызывающую метод wait. Эта ссылка хранится в поле STask. Обратите внимание, что хотя сам метод run не синхронизированный, вызов метода notify выполняется в синхронизированном режиме. В качестве объекта синхронизации выступает задача, для которой вызывается метод notify. Ожидание завершения задачи С помощью метода join вы можете выполнять ожидание завершения работы задачи, для которой этот метод вызван. Существует три определения метода join: public final void join(); public final void join(long millis); public final void join(long millis, int nanos); Первый из них выполняет ожидание без ограничения во времени, для второго ожидание будет прервано принудительно через millis миллисекунд, а для третьего - через millis миллисекунд и nanos наносекунд. Учтите, что реально вы не сможете указывать время с точностью до наносекунд, так как дискретность системного таймера компьютера намного больше. Приложение Synchro Для иллюстрации способа синхронизации задач с помощью методов wait и notify мы подготовили приложение Synchro. Внешне окно аплета этого приложения выглядит точно так же, как и окно аплета Rectangles (рис. 1.2). Напомним, что приложение Rectangles постоянно рисует закрашенные прямоугольники случайного размера, расположения и цвета, для чего в методе run организован бесконечный цикл с задержкой. Приложение Synchro решает ту же самую задачу немного другим способом. Оно создает две задачи. Первая задача рисует в цикле прямоугольники, однако вместо задержки она ожидает извещение от другой задачи, вызывая для этого функцию wait. Вторая задача, опять же в цикле, создает извещения, вызывая с задержкой метод notify. Таким образом, вторая задача как бы управляет работой первой задачи. Исходные тексты приложения Исходные тексты приложения Synchro приведены в листинге 1.11. Листинг 1.11. Файл Synchro\Synchro.javal // ========================================================= // Демонстрация синхронизации двух задач // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // ========================================================= // Основной класс аплета // ========================================================= public class Synchro extends Applet { // Ссылка на задачу рисования прямоугольников DrawRectangles m_DrawRectThread = null; // Ссылка на задачу, периодически разблокирующую задачу // рисования прямоугольников NotifyTask m_NotifyTaskThread = null; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Synchro\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } if (m_NotifyTaskThread == null) { // Создаем задачу, передавая ей ссылку на // задачу рисования прямоугольников, которую // необходимо периодически разблокировать m_NotifyTaskThread = new NotifyTask(m_DrawRectThread); m_NotifyTaskThread.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } if (m_NotifyTaskThread != null) { m_NotifyTaskThread.stop(); m_NotifyTaskThread = null; } } } // ========================================================= // Класс задачи для рисования прямоугольников // ========================================================= class DrawRectangles extends Thread { // Контекст отображения окна аплета Graphics g; // Размеры окна аплета Dimension dimAppWndDimension; // ------------------------------------------------------- // DrawRectangles // Конструктор класса DrawRectangles // ------------------------------------------------------- public DrawRectangles(Applet Appl) { // Получаем и сохраняем контекст отображения g = Appl.getGraphics(); // Определяем текущие размеры окна аплета dimAppWndDimension = Appl.size(); } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он рисует в окне аплета прямоугольники случайного // цвета, размера и расположения // // Этот метод должен быть определен как synchronized // ------------------------------------------------------- public synchronized void run() { while (true) { int x, y, width, height; int rColor, gColor, bColor; // Выбираем случайным образом размеры // и расположение рисуемого прямоугольника x = (int)(dimAppWndDimension.width * Math.random()); y = (int)(dimAppWndDimension.height * Math.random()); width = (int)(dimAppWndDimension.width * Math.random()) / 2; height = (int)(dimAppWndDimension.height * Math.random()) / 2; // Выбираем случайный цвет для рисования // прямоугольника rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); // Устанавливаем выбранный цвет в контексте // отображения g.setColor(new Color(rColor, gColor, bColor)); // Рисуем прямоугольник g.fillRect(x, y, width, height); // Переводим задачу в сотояние ожидания, в котором // она будет находиться до тех пор, пока не будет // разблокирована задачей класса NotifyTask try { Thread.wait(); } catch (InterruptedException e) { } } } } // ========================================================= // Класс задачи для периодического разблокирования // задачи рисования прямоугольников // ========================================================= class NotifyTask extends Thread { // Ссылка на задачу, которую необходимо разблокировать Thread STask; // ------------------------------------------------------- // Конструктор класса NotifyTask // ------------------------------------------------------- public NotifyTask(Thread SynchroTask) { // Сохраняем ссылку на задачу, которую необходимо // разблокировать STask = SynchroTask; } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи и // периодически разблокирует задачу STask // ------------------------------------------------------- public void run() { while (true) { // Выполняем задержку на 30 миллисекунд try { Thread.sleep(30); } catch (InterruptedException e) { } // Получаем объект STask в монопольное владение // и вызываем для него метод notify, // разблокируя работу соотвестсвующей задачи synchronized(STask) { STask.notify(); } } } } Файл документа HTML, созданный автоматически для нашего аплета, вы найдете в листинге 1.12. Листинг 1.12. Файл Synchro\Synchro.html Synchro

The source. Описание исходных текстов Рассмотрим поля и самые важные методы, определенные в классах приложения Synchro. Поля основного класса аплета В основном классе аплета определены две ссылки m_DrawRectThread и m_NotifyTaskThread: DrawRectangles m_DrawRectThread = null; NotifyTask m_NotifyTaskThread = null; Первая из них является ссылкой на объект класса DrawRectangles, определенный нами для задачи рисования прямоугольников. Вторая переменная представляет собой ссылку на объект класса NotifyTask, который создан для задачи, управляющей работой задачи рисования. Метод start основного класса Метод start создает и запускает на выполнение две задачи. Первая задача создается как объект класса DrawRectangles, вторая - как объект класса NotifyTask: if (m_DrawRectThread == null) { m_DrawRectThread = new DrawRectangles(this); m_DrawRectThread.start(); } if (m_NotifyTaskThread == null) { m_NotifyTaskThread = new NotifyTask(m_DrawRectThread); m_NotifyTaskThread.start(); } При создании задачи рисования прямоугольников конструктору передается ссылка на аплет. Эта ссылка нужна задаче для определения размеров окна аплета и получения контекста отображения. Конструктору класса NotifyTask передается ссылка на задачу, работой которой она будет управлять с помощью механизма ожидания извещений. Метод stop основного класса Когда пользователь покидает страницу с аплетом, метод stop останавливает работу обеих задач, вызывая для них метод stop из класса Thread: if (m_DrawRectThread != null) { m_DrawRectThread.stop(); m_DrawRectThread = null; } if (m_NotifyTaskThread != null) { m_NotifyTaskThread.stop(); m_NotifyTaskThread = null; } Поля класса DrawRectangles В поле g класса Graphics хранится контекст отображения окна аплета, определенный конструктором класса DrawRectangles. Поле dimAppWndDimension хранит размеры окна аплета. Конструктор класса DrawRectangles Для получения контекста отображения окна аплета, ссылка на который передается через единственный параметр, конструктор класса DrawRectangles вызывает метод getGraphics: public DrawRectangles(Applet Appl) { g = Appl.getGraphics(); dimAppWndDimension = Appl.size(); } Размеры окна аплета определяются с помощью метода size. Метод run класса DrawRectangles Метод run вызывает метод wait для синхронизации с другой задачей, поэтому этот метод определен как синхронизированный с помощью ключевого слова synchronized: public synchronized void run() { . . . } Внутри метода run организован цикл рисования, который мы уже описывали. После рисования очередного прямоугольника метод run переходит в состояние ожидания извещения, вызывая метод wait: try { Thread.wait(); } catch (InterruptedException e) { } Поля класса NotifyTask В классе NotifyTask мы определили одно поле STask класса Thread, которое хранит ссылку на задачу, работой которой управляет данный класс. Конструктор класса NotifyTask записывает сюда ссылку на задачу рисования прямоугольников. Метод run класса NotifyTask Метод run класса NotifyTask периодически разблокирует задачу рисования прямоугольников, вызывая для этого метод notify в цилке с задержкой 30 миллисекунд. Обращение к объекту STask, который хранит ссылку на задачу рисования прямоугольников, выполняется с использованием синхронизации: synchronized(STask) { STask.notify(); } Задачи-демоны Вызвав для задачи метод setDaemon, вы превращаете обычную задачу в задачу-демон. Такая задача работает в фоновом режиме независимо от породившей ее задачи. Если задача-демон создает другие задачи, то они также станут получат статус задачи-демона. Заметим, что метод setDaemon необходимо вызывать после создания задачи, но до момента ее запуска, то есть перед вызовом метода start. С помощью метода isDaemon вы можете проверить, является задача демоном, или нет. 2 РАБОТА С ФАЙЛАМИ Библиотека классов языка программирования Java содержит многочисленные средства, предназначенные для работы с файлами. И хотя аплеты не имеют доступа к локальным файлам, расположенным на компьютере пользователя, они могут обращаться к файлам, которые находятся в каталоге сервера Web. Автономные приложения Java могут работать как с локальными, так и с удаленными файлами (через сеть Internet или Intranet). В любом случае, будете ли вы создавать автономные приложения Java или аплеты, взаимодействующие с сервером Web через сеть, вы должны познакомиться с классами, предназначенными для организации ввода и вывода. Классы Java для работы с потоками Программист, создающий автономное приложение Java, может работать с потоками нескольких типов: * стандартные потоки ввода и вывода; * потоки, связанные с локальными файлами; * потоки, связанные с файлами в оперативной памяти; * потоки, связанные с удаленными файлами Рассмотрим кратко классы, связанные с потоками. Стандартные потоки Для работы со стандартными потоками в классе System имеется три статических объекта: System.in, System.out и System.err. По своему назначению эти потоки больше всего напоминают стандартные потоки ввода, вывода и вывода сообщений об ошибках операционной системы MS-DOS. Поток System.in связан с клавиатурой, поток System.out и System.err - с консолью приложения Java. Базовые классы для работы с файлами и потоками Количество классов, созданных для работы с файлами, достаточно велико, чтобы привести начинающего программиста в растерянность. Прежде чем мы займемся конкретными классами и приведем примеры приложений, работающих с потоками и файлами, рассмотрим иерархию классов, предназначенных для орагнизации ввода и вывода. Все основные классы, интересующие нас в этой главе, произошли от класса Object (рис. 2.1). Рис. 2.1. Основные классы для работы с файлами и потоками Класс InputStream Класс InputStream является базовым для большого количества классов, на базе которых создаются потоки ввода. Именно эти, производные классы применяются программистами, так как в них имеются намного более мощные методы, чем в классе InputStream. Эти методы позволяют работать с потоком ввода не на уровне отдельных байт, а на уровне объектов различных классов, например, класса String и других. Класс OutputStream Аналогично, класс OutputStream служит в качестве базового для различных классов, имеющих отношение к потокам вывода. Класс RandomAccesFile С помощью класса RandomAccesFile можно организовать работу с файлами в режиме прямого доступа, когда программа указывает смещение и размер блока данных, над которым выполняется операция ввода или вывода. Заметим, кстати, что классы InputStream и OutputStream также можно использовать для обращения к файлам в режиме прямого доступа. Класс File Класс File предназначен для работы с оглавлениями каталогов. С помощью этого класса можно получить список файлов и каталогов, расположенных в заданном каталоге, создать или удалить каталог, переименовать файл или каталог, а также выполнить некоторые другие операции. Класс FileDescriptor C помощью класса FileDescriptor вы можете проверить идентификатор открытого файла. Класс StreamTokenizer Очень удобен класс StreamTokenizer. Он позволяет организовать выделение из входного потока данных элементов, отделенных друг от друга заданными разделителями, такими, например, как запятая, пробел, символы возврата каретки и перевода строки. Производные от класса InputStream От класса InputStream производится много других классов, как это показано на рис. 2.2. Рис. 2.2. Классы, производные от класса InputStream Класс FilterInputStream Класс FilterInputStream, который происходит непосредственно от класса InputStream, является абстрактным классом, на базе которого созданы классы BufferedInputStream, DataInputStream, LineNumberInputStream и PushBackInputStream. Непосредственно класс FilterInputStream не используется в приложениях Java, так как, во-первых, он является абстрактным и предназначен для переопределения методов базового класса InputStream, а во-вторых, наиболее полезные методы для работы с потоками ввода имеются в классах, созданных на базе класса FilterInputStream. Класс BufferedInputStream Буферизация операций ввода и вывода в большинстве случаев значительно ускоряет работу приложений, так как при ее использовании сокращается количество обращений к системе для обмена данными с внешними устройствами. Класс BufferedInputStream может быть использован приложениями Java для организации буферизованных потоков ввода. Заметим, что конструкторы этого класса в качестве параметра получают ссылку на объект класса InputStream. Таким образом, вы не можете просто создать объект класса BufferedInputStream, не создав перед этим объекта класса InputStream. Подробности мы обсудим позже. Класс DataInputStream Составляя программы на языке программирования С, вы были вынуждены работать с потоками на уровне байт или, в лучшем случае, на уровне текстовых строк. Однако часто возникает необходимость записывать в потоки данных и читать оттуда объекты других типов, например, целые числа и числа типа double, числа в формате с плавающей десятичной точкой, массивы байт и символов и так далее. Класс DataInputStream содержит методы, позволяющие извлекать из входного потока данные в перечисленных выше форматах или, как говорят, выполнять форматированный ввод данных. Он также реализует интерфейс DataInput, служащий для этой же цели. Поэтому класс DataInputStream очень удобен и часто применяется в приложениях для работы с потоками ввода. Так же как и конструктор класса BufferedInputStream, конструктор класса DataInputStream должен получить через свои параметр ссылку на объект класса InputStream. Класс LineNumberInputStream С помощью класса LineNumberInputStream вы можете работать с текстовыми потоками, состоящими из отдельных строк, разделенных символами возврата каретки \r и перехода на следующую строку \n. Методы этого класса позволяют следить за нумерацией строк в таких потоках. Класс PushBackInputStream Класс PushBackInputStream позволяет возвратить в поток ввода только что прочитанный оттуда символ, с тем чтобы после этого данный символ можно было прочитать снова. Класс ByteArrayInputStream При необходимости вы можете создать в приложениях Java входной поток данных не на базе локального или удаленного файла, а на базе массива, расположенного в оперативной памяти. Класс ByteArrayInputStream предназначен именно для этого - вы передаете конструктору класса ссылку на массив, и получаете входной поток данных, связанный с этим массивом. Потоки в оперативной памяти могут быть использованы для временного хранения данных. Заметим, что так как аплеты Java не могут обращаться к локальным файлам, для создания временных файлов можно использовать потоки в оперативной памяти на базе класса ByteArrayInputStream. Другую возможность предоставляет класс StringBufferInputStream, рассмотренный ниже. Класс StringBufferInputStream Класс StringBufferInputStream позволяет создавать потоки ввода на базе строк класса String, используя при этом только младшие байты хранящихся в такой строке символов. Этот класс может служить дополнением для класса ByteArrayInputStream, который также предназначен для создания потоков на базе данных из оперативной памяти. Класс FileInputStream Этот класс позволяет создать поток ввода на базе класса File или FileDescriptor. Класс PipedInputStream С помощью классов PipedInputStream и PipedOutputStream можно организовать двухстороннюю передачу данных между двумя одновременно работающими задачами мультизадачного аплета. Класс SequenceInputStream Класс SequenceInputStream позволяет объединить несколько входных потоков в один поток. Если в процессе чтения будет достигнут конец первого потока такого объединения, в дальнейшем чтение будет выполняться из второго потока и так далее. Производные от класса OutputStream Класс OutputStream предназначен для создания потоков вывода. Приложения, как правило, непосредственно не используют этот класс для операций вывода, так же как и класс InputStream для операций ввода. Вместо этого применяются классы, иерархия которых показана на рис. 2.3. Рис. 2.3. Классы, производные от класса OutputtStream Рассмотрим кратко назначение этих классов. Класс FilterOutputStream Абстрактный класс FilterOutputStream служит прослойкой между классом OutputStream и классами BufferedOutputStream, DataOutputStream, а также PrintStream. Он выполняет роль, аналогичную роли рассмотренного ранее класса FilterIntputStream. Класс BufferedOutputStream Класс BufferedOutputStream предназначен для создания буферизованных потоков вывода. Как мы уже говорили, буферизация ускоряет работу приложений с потоками. Класс DataOutputStream С помощью класса DataOutputStream приложения Java могут выполнять форматированный вывод данных. Для ввода форматированных данных вы должны создать входной поток с использованием класса DataInputStream, о котором мы уже говорили. Класс DataOutputStream реализует интерфейс DataOutput. Класс PrintStream Потоки, созданные с использованием класса PrintStream, предназначены для форматного вывода данных различных типов с целью их визуального представления в виде текстовой строки. Аналогичная операция в языке программирования С выполнялась функцией printf. Класс ByteArrayOutputStream С помощью класса ByteArrayOutputStream можно создать поток вывода в оперативной памяти. Класс FileOutputStream Этот класс позволяет создать поток вывода на базе класса File или FileDescriptor. Класс PipedOutputStream Как мы уже говорили, классы PipedInputStream и PipedOutputStream предназначены для организации двухсторонней передачи данных между двумя одновременно работающими задачами мультизадачного аплета. Работа со стандартными потоками Приложению Java доступны три стандратных потока, которые всегда открыты: стандартный поток ввода, стандартный поток вывода и стандартный поток вывода сообщений об ошибках. Все перечисленные выше потоки определены в классе System как статические поля с именами, соответственно, in, out и err: public final class java.lang.System extends java.lang.Object { public static PrintStream err; public static InputStream in; public static PrintStream out; . . . } Заметим, что стандратные потоки, как правило, не используются аплетами, так как навигаторы Internet общаются с пользователем через окно аплета и извещения от мыши и клавиатуры, а не через консоль. Стандартный поток ввода Стандартный поток ввода in определен как статический объект класса InputStream, который содержит только простейшие методы для ввода данных. Нужнее всего вам будет метод read: public int read(byte b[]); Этот метод читает данные из потока в массив, ссылка на который передается через единственный параметр. Количество считанных данных определяется размером массива, то есть значением b.length. Метод read возвращает количество прочитанных байт данных или -1, если достигнут конец потока. При возникновении ошибок создается исключение IOException, обработку которого необходимо предусмотреть. Стандартный поток вывода Стандартный поток вывода out создан на базе класса PrintStream, предназначенного, как мы это отмечали раньше, для форматированного вывода данных различного типа с целью их визуального отображения в виде текстовой строки. Для работы со стандартным потоком вывода вы будете использовать главным образом методы print и println, хотя метод write также доступен. В классе PrintStream определено несколько реализаций метода print с параметрами различных типов: public void print(boolean b); public void print(char c); public void print(char s[]); public void print(double d); public void print(float f); public void print(int i); public void print(long l); public void print(Object obj); public void print(String s); Как видите, вы можете записать в стандартный поток вывода текстовое представление данных различного типа, в том числе и класса Object. Метод println аналогичен методу print, отличаясь лишь тем, что он добавляет к записываемой в поток строке символ перехода на следующую строку: public void println(); public void println(boolean b); public void println(char c); public void println(char s[]); public void println(double d); public void println(float f); public void println(int i); public void println(long l); public void println(Object obj); public void println(String s); Реализация метода println без параметров записывает только символ перехода на следующую строку. Стандртный поток вывода сообщений об ошибках Стандртный поток вывода сообщений об ошибках err так же, как и стадартный поток вывода out, создан на базе класса PrintStream. Поэтому для записи сообщений об ошибках вы можете использовать только что описанные методы print и println. Приложение Standard Приложение Standard демонстрирует способы работы со стандартными потоками Java. Это консольное приложение, а не аплет. При запуске приложение Standard выводит строку Hello, Java! И приглашение для ввода строки (рис. 2.4). Рис. 2.4. Консольное окно приложения Standard Если ввести любую текстовую строку и нажать клавишу , введенная строка появится на консоли. Далее появится сообщение о том, что для завершения работы приложения нужно снова нажать клавишу . Исходный текст приложения Исходный текст приложения приведен в листинге 2.1. После трансляции исходного текста вы можете запустить его на выполнение непосредственно из среды разработки приложений Microsoft Visual J++. При этом, когда на экране появится диалоговая панель Information For Running Class (рис. 2.5), вы должны указать в поле Class file name имя Standard, а в поле Run project under включить переключатель Stand-alone interpreter. Рис. 2.5. Заполнение диалоговой панели Information For Running Class При этом приложение будет запущено под управлением автономного интерпретатора Java jview.exe. Листинг 2.1. Файл Standard\Standard.java // ========================================================= // Демонстрация использования стандартных потоков // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; public class Standard { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка String sOut; // Выполняем попытку вывода на консоль // строки приглашения try { // Выводим строку приглашения System.out.println("Hello, Java!\n" + "Enter string and press ..."); // Читаем с клавиатуры строку System.in.read(bKbdInput); // Преобразуем введенные символы в строку типа String sOut = new String(bKbdInput, 0); // Выводим только что введенную строку на консоль System.out.println(sOut); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.err.println(ioe.toString()); } // Ожидаем ввода с клавиатуры, а затем завершаем // работу приложения try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.err.println(ioe.toString()); } } } Описание исходного текста Структура приложения Standard очень проста. В нем определен один класс с именем Standard типа public, и один метод с имененм main: public class Standard { public static void main(String args[]) { . . . } } Напомним, что имена класса и файла .class должны совпадать. Сразу после запуска автономного приложения Java управление передается функции main. Внутри этой функции мы определили массив bKbdInput типа byte и строку sOut: byte bKbdInput[] = new byte[256]; String sOut; Созданный оператором new массив имеет размер 256 байт и предназначен для хранения строки, введенной пользователем при помощи клавиатуры. В дальнейшем содержимое этого массива преобразуется в строку sOut. Первое, что делает наше приложение после создания массива, это вывод на консоль текстовой строки приглашения: System.out.println("Hello, Java!\n" + "Enter string and press ..."); Здесь вызывается метод println для статического объекта out класса PrintStream, который, как вы знаете, определен в классе System. На следующем этапе приложение читает из стандартного потока ввода in, вызывая для этого метод read: System.in.read(bKbdInput); Стандартный поток ввода связан с клавиатурой, поэтому приложение перейдет в состояние ожидания до тех пор, пока пользователь не введет текстовую строку, нажав после чего клавишу . Введенная строка отображается на консоли, для чего она записывается в стандартный поток вывода методом println: System.out.println(sOut); При выполнении операций с потоками ввода или вывода могут возникать исключения, поэтому в нашем приложении предусмотрены обработчики исключений: catch(Exception ioe) { System.err.println(ioe.toString()); } При возникновении любого исключения в стандартный поток вывода сообщений об ошибках записывается текстовая строка названия класса возникнувшего исключения. Для того чтобы вы смогли посмотреть на результаты работы приложения, после отображения на консоли введенной строки приложение вновь вызывается метод read для стандартного потока ввода. Для завершения работы приложения пользователь должен нажать клавишу . Создание потоков, связанных с файлами Если вам нужно создать входной или выходной поток, связанный с локальным файлом, следует воспользоваться классами из библиотеки Java, созданными на базе классов InputStream и OutputStream. Мы уже кратко рассказывали об этих классах в разделе “Классы Java для работы с потоками”. Однако методика использования перечисленных в этом разделе классов может показаться довольно странной. В чем эта странность? Говоря кратко, странность заключается в том, что для создания потока вам необходимо воспользоваться сразу несколькими классами, а не одним, наиболее подходящим для решения поставленной задачи, как это можно было бы предположить. Поясним сказанное на примере. Пусть, например, нам нужен выходной поток для записи форматированных данных (скажем, текстовых строк класса String). Казалось бы, достаточно создать объект класса DataOutputStream, - и дело сделано. Однако не все так просто. В классе DataOutputStream предусмотрен только один конструктор, которому в качестве параметра необходимо передать ссылку на объект класса OutputStream: public DataOutputStream(OutputStream out); Что же касается конструктора класса OutputStream, то он выглядит следующим образом: public OutputStream(); Так как ни в том, ни в другом конструкторе не предусмотрено никаких ссылок на файлы, то непонятно, как с использованием только одних классов OutputStream и DataOutputStream можно создать выходной поток, связанный с файлом. Что же делать? Создание потока для форматированного обмена данными Оказывается, создание потоков, связанных с файлами и предназначенных для форматированного ввода или вывода, необходимо выполнять в несколько приемов. При этом вначале необходимо создать потоки на базе класса FileOutputStream или FileInputStream, а затем передать ссылку на созданный поток констркутору класса DataOutputStream или DataInputStream. В классах FileOutputStream и FileInputStream предусмотрены конструкторы, которым в качестве параметра передается либо ссылка на объект класса File, либо ссылка на объект класса FileDescriptor, либо, наконец, текстовая строка пути к файлу: public FileOutputStream(File file); public FileOutputStream(FileDescriptor fdObj); public FileOutputStream(String name); Таким образом, если вам нужен выходной поток для записи форматированных данных, вначале вы создаете поток как объект класса FileOutputStream. Затем ссылку на этот объект следует передать конструктору класса DataOutputStream. Полученный таким образом объект класса DataOutputStream можно использовать как выходной поток, записывая в него форматированные данные. Добавление буферизации А что, если нам нужен не простой выходной поток, а буферизованный? Здесь нам может помочь класс BufferedOutputStream. Вот два конструктора, предусмотренных в этом классе: public BufferedOutputStream(OutputStream out); public BufferedOutputStream(OutputStream out, int size); Первый из них создает буферизованный выходной поток на базе потока класса OutputStream, а второй делает то же самое, но дополнительно позволяет указать размер буфера в байтах. Если вам нужно создать выходной буферизованный поток для записи форматированных данных, создание потока выполняется в три приема: * создается поток, связанный с файлом, как объект класса FileOutputStream; * ссылка на этот поток передается конструктору класса BufferedOutputStream, в результате чего создается буферизованный поток, связанный с файлом; * ссылка на буферизованный поток, созданный на предыдущем шаге, передается конструктору класса DataOutputStream, который и создает нужный поток Вот фрагмент исходного текста программы, который создает выходной буферизованный поток для записи форматированных данных в файл с именем output. txt: DataOutputStream OutStream; OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.txt"))); Аналогичным образом создается входной буферизованный поток для чтения форматированных данных из того же файла: DataInputStream InStream; InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt"))); Исключения при создании потоков При создании потоков на базе классов FileOutputStream и FileInputStream могут возникать исключения FileNotFoundException, SecurityException, IOException. Исключение FileNotFoundException возникает при попытке открыть входной поток данных для несуществующего файла, то есть когда файл не найден. Исключение SecurityException возникает при попытке открыть файл, для которого запрещен доступ. Например, если файл можно только читать, а он открывается для записи, возникнет исключение SecurityException. Если файл не может быть открыт для записи по каким-либо другим причинам, возникает исключение IOException. Запись данных в поток и чтение данных из потока Для обмена данными с потоками можно использовать как простейшие методы write и read, так и методы, допускающие ввод или вывод форматированных данных. В зависимости от того, на базе какого класса создан поток, зависит набор доступных методов, предназначенных для чтения или записи данных. Простейшие методы Создав выходной поток на базе класса FileOutputStream, вы можете использовать для записи в него данных три разновидности метода write, прототипы которых представлены ниже: public void write(byte b[]); public void write(byte b[], int off, int len); public void write(int b); Первый из этих методов записывает в поток содержимое массива, ссылка на который передается через параметр, начиная с текущей позиции в потоке. После выполнения записи текущая позиция продвигается вперед на число записанных байт, которое при успешном завершении операции равно длине массива (b.length). Второй метод позволяет дополнительно указать начальное смещение off записываемого блока данных в массиве и количество записываемых байт len. Третий метод просто записывает в поток один байт данных. Если в процессе записи происходит ошибка, возникает исключение IOException. Для входного потока, созданного на базе класса FileInputStream, определены три разновидности метода read, выполняющего чтение данных: public int read(); public int read(byte b[]); public int read(byte b[], int off, int len); Первая разновидность просто читает из потока один байт данных. Если достигнут конец файла, возвращается значение -1. Вторая разновидность метода read читает данные в массив, причем количество прочитанных данных определяется размером массива. Метод возвращает количество прочитанных байт данных или значение -1, если в процессе чтения был достигнут конец файла. И, наконец, третий метод позволяет прочитать данные в область массива, заданную своим смещением и длиной. Если при чтении происходит ошибка, возникает исключение IOException. Методы для чтения и записи форматированных данных Вместо того чтобы записывать в потоки и читать оттуда отдельные байты или массивы байт, программисты обычно предпочитают пользоваться намного более удобными методами классов DataOutputStream и DataInputStream, допускающими форматированный ввод и вывод данных. Вот, например, какой набор методов можно использовать для записи форматированных данных в поток класса DataOutputStream: public final void writeBoolean(boolean v); public final void writeByte(int v); public final void writeBytes(String s); public final void writeChar(int v); public final void writeChars(String s); public final void writeDouble(double v); public final void writeFloat(float v); public final void writeInt(int v); public final void writeLong(long v); public final void writeShort(int v); public final void writeUTF(String s); Хотя имена методов говорят сами за себя, сделаем замечания относительно применения некоторых из них. Метод writeByte записывает в поток один байт. Это младший байт слова, которое передается методу через параметр v. В отличие от метода writeByte, метод writeChar записывает в поток двухбайтовое символьное значение (напомним, что в Java символы хранятся с использованием кодировки Unicode и занимают два байта). Если вам нужно записать в выходной поток текстовую строку, то это можно сделать с помощью методов writeBytes, writeChars или writeUTF. Первый из этих методов записывает в выходной поток только младшие байты символов, а второй - двухбайтовые символы в кодировке Unicode. Метод writeUTF предназначен для записи строки в машинно-независимой кодировке UTF-8. Все перечисленные выше методы в случае возникновения ошибки создают исключение IOException, которое вы должны обработать. В классе DataInputStream определены следующие методы, предназначенные для чтения форматированных данных из входного потока: public final boolean readBoolean(); public final byte readByte(); public final char readChar(); public final double readDouble(); public final float readFloat(); public final void readFully(byte b[]); public final void readFully(byte b[], int off, int len); public final int readInt(); public final String readLine(); public final long readLong(); public final short readShort(); public final int readUnsignedByte(); public final int readUnsignedShort(); public final String readUTF(); public final static String readUTF(DataInput in); public final int skipBytes(int n); Обратите внимание, что среди этих методов нет тех, что специально предназначены для четния данных, записанных из строк методами writeBytes и writeChars класса DataOutputStream. Тем не менее, если входной поток состоит из отдельных строк, разделенных символами возврата каретки и перевода строки, то такие строки можно получить методом readLine. Вы также можете воспользоваться методом readFully, который заполняет прочитанными данными массив байт. Этот массив потом будет нетрудно преобразовать в строку типа String, так как в классе String предусмотрен соответствующий конструктор. Для чтения строк, записанных методом writeUTF вы должны обязательно пользоваться методом readUTF. Метод skipBytes позволяет пропустить из входного потока заданное количество байт. Методы класса DataInputStream, предназначенные для чтения данных, могут создавать исключения IOException и EOFException. Первое из них возникает в случае ошибки, а второе - при достижении конца входного потока в процессе чтения. Закрывание потоков Работая с файлами в среде MS-DOS или Microsoft Windows средствами языка программирования С вы должны были закрывать ненужные более файлы. Так как в системе интерпертации приложений Java есть процесс сборки мусора, возникает вопрос - выполняет ли он автоматическое закрывание потоков, с которыми приложение завершило работу? Оказывается, процесс сборки мусора не делает ничего подобного! Сборка мусора выполняется только для объектов, размещенных в оперативной памяти. Потоки вы должны закрывать явным образом, вызывая для этого метод close. Принудительный сброс буферов Еще один важный момент связан с буферизованными потоками. Как мы уже говорили, буферизация ускоряет работу приложений с потоками, так как при ее использовании сокращается количество обращений к системе ввода/вывода. Вы можете постепенно в течении дня добавлять в поток данные по одному байту, и только к вечеру эти данные будут физически записаны в файл на диске. Во многих случаях, однако, приложение должно, не отказываясь совсем от буферизации, выполнять принудительную запись буферов в файл. Это можно сделать с помощью метода flush. Приложение StreamDemo Приложение StreamDemo было создано нами специально для того чтобы продемонстрировать наиболее распространенный способ работы с файлами через буферизованные форматированные потоки. Действия, выполняемые этим приложением, достаточно просты. Вначале приложение выводит на консоль приглашение для ввода строки. Затем введенная строка записывается в файл через выходной буферизованный форматированный поток. После этого созданный файл читается через входной буферизованный поток. Его содержимое (введенная ранее строка) отображается на экране. Исходный текст приложения Исходный текст приложения StreamDemo представлен в листинге 2.2. Листинг 2.2. Файл StreamDemo\StreamDemo.java // ========================================================= // Демонстрация использования потоков для работы с файлами // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; public class StreamDemo { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Выходной поток DataOutputStream OutStream; // Входной поток DataInputStream InStream; // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка, которая будет записана в поток String sOut; // Выполняем попытку вывода на консоль строки // приглашения try { // Выводим строку приглашения System.out.println("Hello, Java!\n" + "Enter string and press ..."); // Читаем с клавиатуры строку для записи в файл System.in.read(bKbdInput); // Преобразуем введенные символы в строку типа String sOut = new String(bKbdInput, 0); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } // Выполняем попытку записи в выходной поток try { // Создаем выходной буферизованный поток данных OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.txt"))); // Записываем строку sOut в выходной поток OutStream.writeBytes(sOut); // Сбрасываем содержимое буфера вывода OutStream.flush(); // Закрываем выходной поток OutStream.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } // Выполняем попытку чтения из файла try { // Создаем входной буферизованный поток данных InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt"))); // Читаем одну строку из созданного входного потока // и отображаем ее на консоли System.out.println(InStream.readLine()); // Закрываем входной поток InStream.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } // Ожидаем ввода с клавиатуры, а затем завершаем // работу приложения try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста приложения Внутри метода main мы создали ссылки на выходной поток OutStream и входной поток InStream: DataOutputStream OutStream; DataInputStream InStream; Кроме того, мы предусмотрели массив для ввода информации из стандартного входного потока: byte bKbdInput[] = new byte[256]; Способ вывода приглашения и получения строки с клавиатуры ничем не отличается от использованного в предыдущем приложении, поэтому для экономии места мы не будем на нем останавливаться. Скажем только, что введенная строка записывается в поле sOut типа String. Создание выходного потока для записи строки выполняется следующим образом: OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.txt"))); Вначале с помощью конструктора создается объект класса FileOutputStream - поток, связанный с файлом output.txt. Далее на базе этого потока создается буферизованный поток типа BufferedOutputStream. И, наконец, на базе буферизованного потока создается форматированный поток OutStream класса DataOutputStream. Заметим, что конструктор класса FileOutputStream создает файл output.txt, если он не существует, и перезаписывает существующий файл. Если вам нужно исключить случайную перезапись существующего файла, необходимо воспользоваться классом File, о котором мы еще будем рассказывать. Для записи строки sOut в выходной поток мы вызываем метод writeBytes: OutStream.writeBytes(sOut); После записи выполняется сброс содержимого буферов и закрытие потока: OutStream.flush(); OutStream.close(); При закрытии потока содержимое буферов сбрасывается автоматически. Мы вызвали метод flush только для иллюстрации. При работе с потоками могут возникать различные исключения. Наш обработчик этих исключений просто записывает в стандратный поток вывода название возникшего исключения: catch(Exception ioe) { System.out.println(ioe.toString()); } На следующем этапе приложение открывает файл output.txt для чтения буферизованным форматированным потоком: InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt"))); В этой последовательности создания трех объектов различных классов для вас нет ничего нового. Вначале мы создаем поток ввода, связанный с файлом, затем на его базе - буферизованный входной поток, и, наконец, на базе буферизованного входного потока - буферизованный форматированный входной поток. Для чтения строки из входного потока мы применили метод readLine: System.out.println(InStream.readLine()); Прочитанная строка сразу отображается на консоли, для чего она записывается в стандартный поток вывода. После завершения работы со входным потоком мы закрываем его методом close: InStream.close(); На последнем этапе приложение ожидает ввода с клавиатуры и затем завершает свою работу. Потоки в оперативной памяти Операционные системы Microsoft Windows 95 и Microsoft Windows NT предоставляют возможность для программиста работать с оперативной памятью как с файлом. Это очень удобно во многих случаях, в частности, файлы, отображаемые на память, можно использовать для передачи данных между одновременно работающими задачами и процессами. Подробнее об этом вы можете прочитать в 27 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows NT для программиста. Часть 2”. При создании приложений и аплетов Java вы также можете работать с объектами оперативной памяти, как с файлами, а точнее говоря, как с потоками. Так как аплетам запрещено обращаться к файлам, расположенным на локальном диске компьютера, при небходимости создания временных потоков ввода или вывода последние могут быть размещены в оперативной памяти. Ранее мы отмечали, что в библиотеке классов Java есть три класса, специально предназначенных для создания потоков в оперативной памяти. Это классы ByteArrayOutputStream, ByteArrayInputStream и StringBufferInputStream. Класс ByteArrayOutputStream Класс ByteArrayOutputStream создан на базе класса OutputStream. В нем имеется два конструктора, прототипы которых представлены ниже: public ByteArrayOutputStream(); public ByteArrayOutputStream(int size); Первый из этих конструкторов создает выходной поток в оперативной памяти с начальным размером буфера, равным 32 байта. Второй позволяет указать необходимый размер буфера. В классе ByteArrayOutputStream определено несколько достаточно полезных методов. Вот некоторые из них: public void reset(); public int size(); public byte[] toByteArray(); public void writeTo(OutputStream out); Метод reset сбрасывает счетчик байт, записанных в выходной поток. Если данные, записанные в поток вам больше не нужны, вы можете вызвать этот метод и использовать память, выделенную для потока, для записи других данных. С помощью метода size можно определить количество байт данных, записанных в поток. Метод toByteArray позволяет скопировать данные, записанные в поток, в массив байт. Этот метод возвращает адрес созданного для этой цели массива. С помощью метода writeTo вы можете скопировать содержимое данного потока в другой выходной поток, ссылка на который передается методу через параметр. Для выполнения форматированного вывода в поток, вы должны создать поток на базе класса DataOutputStream, передав соответствующему конструктору ссылку на поток класса ByteArrayOutputStream. Класс ByteArrayInputStream С помощью класса ByteArrayInputStream вы можете создать входной поток на базе массива байт, расположенного в оперативной памяти. В этом классе определено два конструктора: public ByteArrayInputStream(byte buf[]); public ByteArrayInputStream(byte buf[], int offset, int length); Первый конструктор получает через единственный параметр ссылку на массив, который будет использован для создания входного потока. Второй позволяет дополнительно указать смещение offset и размер области памяти length, которая будет использована для создания потока. Вот несколько методов, определенных в классе ByteArrayInputStream: public int available(); public int read(); public int read(byte b[], int off, int len); public void reset(); public long skip(long n); Наиболее интересен из них метод available, с помощью которого можно определить, сколько байт имеется во входном потоке для чтения. Обычно класс ByteArrayInputStream используется вместе с классом DataInputStream, что позволяет организовать форматный ввод данных. Класс StringBufferInputStream Класс StringBufferInputStream предназначен для создания входного потока на базе текстовой строки класса String. Ссылка на эту строку передается конструктору класса StringBufferInputStream через параметр: public StringBufferInputStream(String s); В классе StringBufferInputStream определены те же методы, что и в только что рассмотренном классе ByteArrayInputStream. Для более удобной работы вы, вероятно, создадите на базе потока класса StringBufferInputStream поток класса DataInputStream. Приложение MemStream Аплет MemStream создает два потока в оперативной памяти - выходной и входной. Вначале во время инициализации метод init создает выходной поток и записывает в него текстовую строку “Hello, Java!”. Содержимое этого потока затем копируется в массив, и на базе этого массива создается входной поток. Во время рисования окна аплета метод paint создает из только что упомянутого массива входной поток, читает из этого потока одну строку и отображает ее в окне аплета. Аплет не может работать с обычными локальными файлами, поэтому для выполнения описанных выше действий необходимы потоки в оперативной памяти. Исходные тексты приложения Исходный текст аплета MemStream приведен в листинге 2.3. Листинг 2.3. Файл MemStream\MemStream.java // ========================================================= // Работа с потоками в оперативной памяти // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; import java.io.*; public class MemStream extends Applet { // Выходной поток DataOutputStream OutStream; // Входной поток DataInputStream InStream; // Строка, которая будет записана в поток String sOut; // Массив, в который будет копироваться содержимое // выходного потока byte[] bMemStream; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: MemStream\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод init, получает управление при инициализации // аплета // ------------------------------------------------------- public void init() { // Инициализируем строку для записи в поток sOut = "Hello, Java!"; try { // Создаем выходной поток в оперативной памяти ByteArrayOutputStream baStream = new ByteArrayOutputStream(255); // Создаем буферизованный форматированный поток // на базе потока baStream OutStream = new DataOutputStream( new BufferedOutputStream(baStream)); // Записываем строку sOut в выходной поток OutStream.writeBytes(sOut); // Сбрасываем содержимое буфера OutStream.flush(); // Копируем содержимое потока в массив bMemStream bMemStream = baStream.toByteArray(); // Закрываем выходной поток OutStream.close(); } catch(Exception ioe) { } } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); try { // Создаем входной буферизованный поток данных InStream = new DataInputStream( new BufferedInputStream( new ByteArrayInputStream(bMemStream))); // Читаем одну строку из созданного входного потока // и отображаем ее g.drawString(InStream.readLine(), 10, 20); // Закрываем входной поток InStream.close(); } catch(Exception ioe) { } } } В листинге 2.4 вы найдете исходный текст документа HTML, созданный автоматически для нашего аплета системой Microsoft Visual J++. Листинг 2.4. Файл MemStream\MemStream.html MemStream

The source. Описание исходных текстов Мы рассмотрим только самые важные методы нашего аплета - init и paint. Метод init В начале своей работы метод init записывает в поле sOut текстовую строку, которая будет записана в выходной поток: String sOut; sOut = "Hello, Java!"; Далее метод init создает поток baStream класса ByteArrayOutputStream, устанавливая начальный размер выделенного для него массива равным 255 байт: ByteArrayOutputStream baStream = new ByteArrayOutputStream(255); Для выполнения форматированного вывода нам нужен поток класса DataOutputStream, который мы и создаем на базе потока baStream: OutStream = new DataOutputStream( new BufferedOutputStream(baStream)); Для записи строки в выходной поток мы воспользовались методом writeBytes: OutStream.writeBytes(sOut); Так как наш выходной поток буферизован, после вызова метода writeBytes данные могут остаться в промежуточном буфере, не достигнув массива, выделенного для хранения потока. Чтобы переписать данные из буфера в массив, мы выполняем сброс буфера методом flush: OutStream.flush(); После сброса буфера (и только после этого) можно копировать содержимое потока методом toByteArray: bMemStream = baStream.toByteArray(); Этот метод возвращает ссылку на созданный массив, которую мы записываем в поле bMemStream. В дальнейшем на базе этого массива мы создадим поток ввода. Перед завершением своей работы метод init закрывает входной поток,вызывая метод close: OutStream.close(); Метод paint После традиционного для наших аплетов раскрашивания окна и рисования рамки метод paint создает входной буферизованный поток на базе массива bMemStream: InStream = new DataInputStream( new BufferedInputStream( new ByteArrayInputStream(bMemStream))); Поток создается в три этапа с помощью классов ByteArrayInputStream, BufferedInputStream и DataInputStream. Далее мы читаем из созданного таким образом входного потока одну строку, вызывая для этого метод readLine: g.drawString(InStream.readLine(), 10, 20); Прочитанная строка отображается в окне аплета методом drawString. После завершения работы с потоком мы его закрываем, для чего вызываем метод close: InStream.close(); Класс StreamTokenizer для разбора входных потоков Если вы создаете приложение, предназначенное для обработки текстов (например, транслятор или просто разборщик файла конфигурации, содержащего значения различных параметров), вам может пригодиться класс StreamTokenizer. Создав объект этого класса для входного потока, вы можете легко решить задачу выделения из этого потока отдельных слов, символов, чисел и строк комментариев. Конструктор класса StreamTokenizer Для создание объектов класса StreamTokenizer предусмотрен всего один конструктор: public StreamTokenizer(InputStream istream); В качестве параметра этому конструктору необходимо передать ссылку на заранее созданный входной поток. Методы класса StreamTokenizer Для настройки параметров разборщика StreamTokenizer и получения отдельных элементов входного потока вы должны пользоваться методами, определенными в классе StreamTokenizer. Рассмотрим самые важние из них. Методы для настройки параметров разборщика Ниже мы привели прототипы методов, предназначенных для настройки параметров разборщика: public void commentChar(int ch); public void slashSlashComments(boolean flag); public void slashStarComments(boolean flag); public void quoteChar(int ch); public void eolIsSignificant(boolean flag); public void lowerCaseMode(boolean fl); public void ordinaryChar(int ch); public void ordinaryChars(int low, int hi); public void resetSyntax(); public void parseNumbers(); public void whitespaceChars(int low, int hi); public void wordChars(int low, int hi); Несколько методов определяют, будет ли разборщик выделять во входном потоке строки комментария и если будет, то какми образом. С помощью метода commentChar вы можете указать символ комментария. Если в строке входного потока попадется такой символ, то он и все следующие за ним до конца текущей строки символы будут проигнорированы. Методы SlashSlashComments и slashStarComments позволяют указать, что для входного текста используются разделители комментариев в виде двойного символа ‘/’ и ‘/* … */’, соответственно. Это соответствует способу указания комментариев в программах, составленных на языках программирования С++ и С. Для включения режима выделения комментариев обоим методам в качетстве параметра необходимо передать значение true, а для отключения - false. Метод quoteChar позволяет задать символ, который будет использован в качестве кавычек. Когда при разборе потока встречаются слова, взятые в кавычки, они возвращаются программе разбора без кавычек. Если передать методу eolIsSignificant значение true, разделители строк будут интерпретироваться как отдельные элементы. Если же этому методу передать значение false, разделители строк будут использоваться аналогично пробелам для разделения элементов входного потока. Метод lowerCaseMode позволяет включить режим, при котором все выделенные элементы будут перекодированы в строчные символы. Методы ordinaryChar и ordinaryChars позволяют указать символы, которые должны интерпретироваться как обычные, из которых составляются слова или цифры. Например, если передать методу ordinaryChar символ ‘.’, то слово java.io будет восприниматься как один элемент. Если же этого не сделать, то разборщик выделит из него три элемента - слово java, точку ‘.’ и слово io. Метод ordinaryChars позволяет указать диапазон значений символов, которые должны интерпретироваться как обычные. С помощью метода resetSyntax вы можете указать, что все символы будут рассматриваться, как обычные. Метод parseNumbers включает режим разбора чисел, при котором распознаются и преобразуются числа в формате с плавающей десятичной точкой. Метод whitespaceChars задает диапазон значений для символов-разделителей отдельных слов в потоке. Метод wordChars позволяет указать символы. Которые являются составными частями слов. Методы для разбора входного потока После того как вы создали разборщик входного потока на базе класса StreamTokenizer и установили его параметры с помощью описанных выше методов, можно приступать собственно к разборке потока. Обычно для этого организуется цикл, в котором вызывается метод nextToken: public int nextToken(); Этот метод может вернуть одно из следующих значений: ЗначениеОписаниеTT_WORDИз потока было извлечено словоTT_NUMBERИз потока было извлечено численное значениеTT_EOLОбнаружен конец строки. Это значение возвращается только в том случае, если при настройке параметров разборщика был вызван метод eolIsSignficantTT_EOFОбнаружен конец файла Если метод nextToken вернул значение TT_EOF, следует завершить цикл разбора входного потока. Как извлечь считанные элементы потока? В классе StreamTokenizer определено три поля: public String sval; public double nval; public int ttype; Если метод nextToken вернул значение TT_WORD, в поле sval содержится извлеченный элемент в виде текстовой строки. В том случае, когда из входного потока было извлечено числовое значение, оно будет храниться в поле nval типа double. Обычные символы записываются в поле ttype. Заметим, что если в потоке обнаружены слова, взятые в кавычки, то символ кавычки записывается в поле ttype, а слова - в поле sval. По умолчанию используется символ кавычек ‘”’, однако с помощью метода quoteChar вы можете задать любой другой символ. При необходимости в процессе разбора вы можете определить номер текущей строки, вызвав для этого метод lineno: public int lineno(); После вызова метода pushBack следующий вызов метода nextToken приведет к тому, что в поле ttype будет записано текущее значение, а содержимое полей sval и nval не изменится. Прототип метода pushBack приведен ниже: public void pushBack(); Метод toString возвращает текстовую строку, представляющую текущий элемент, выделенный из потока: public String toString(); Приложение StreamToken В приложении StreamToken мы демонстрируем использование класса StreamTokenizer для разбора входного потока. Вначале приложение запрашивает у пользователя строку для разбора, записывая ее в файл. Затем этот файл открывается для чтения буферизованным потоком и разбирается на составные элементы. Каждый такой элемент выводится в отдельной строке, как это показано на рис. 2.6. Рис. 2.6. Разбор входного потока в приложении StreamToken Обратите внимание, что в процессе разбора значение 3.14 было воспринято как числовое, а 3,14 - нет. Это потому, что при настройке разборщика мы указали, что символ ‘.’ является обычным. Исходный текст приложения Исходный текст приложения StreamToken представлен в листинге 2.5. Листинг 2.5. Файл StreamToken\StreamToken.java // ========================================================= // Разбор входного потока при помощи класса // StreamTokenizer // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; // ========================================================= // Класс StreamToken // Главный класс приложения // ========================================================= public class StreamToken { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Выходной поток DataOutputStream OutStream; // Входной поток DataInputStream InStream; // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка, которая будет записана в поток String sOut; try { // Выводим строку приглашения System.out.println("Enter string to parse..."); // Читаем с клавиатуры строку для записи в файл System.in.read(bKbdInput); // Преобразуем введенные символы в строку типа String sOut = new String(bKbdInput, 0); // Создаем выходной буферизованный поток данных OutStream = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.txt"))); // Записываем строку sOut в выходной поток OutStream.writeBytes(sOut); // Закрываем выходной поток OutStream.close(); // Создаем входной буферизованный поток данных InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt"))); // Создаем объект для разбора потока TokenizerOfStream tos = new TokenizerOfStream(); // Выполняем разбор tos.TokenizeIt(InStream); // Закрываем входной поток InStream.close(); System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } // ========================================================= // Класс TokenizerOfStream // Класс для разбора входного потока // ========================================================= class TokenizerOfStream { public void TokenizeIt(InputStream is) { // Создаем разборщик потока StreamTokenizer stok; // Временная строка String str; try { // Создаем разборщик потока stok = new StreamTokenizer(is); // Задаем режим исключения комментариев, // записанных в стиле С++ (два символа '/') stok.slashSlashComments(true); // Указываем , что символ '.' будет обычным символом stok.ordinaryChar('.'); // Запускаем цикл разбора потока, // который будет завершен при достижении // конца потока while(stok.nextToken() != StreamTokenizer.TT_EOF) { // Определяем тип выделенного элемента switch(stok.ttype) { // Если это слово, записываем его во // временную строку case StreamTokenizer.TT_WORD: { str = new String("\nTT_WORD >" + stok.sval); break; } // Если это число, преобразуем его // в строку case StreamTokenizer.TT_NUMBER: { str = "\nTT_NUMBER >" + Double.toString(stok.nval); break; } // Если найден конец строки, // выводим строку End of line case StreamTokenizer.TT_EOL: { str = new String("> End of line"); break; } // Выводим прочие символы default: { if((char)stok.ttype == '"') { str = new String("\nTT_WORD >" + stok.sval); } else str = "> " + String.valueOf((char)stok.ttype); } // Выводим на консоль содержимое временной строки System.out.println(str); } } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста приложения После ввода строки с клавиатуры и записи ее в файл через поток наше приложение создает входной буферизованный поток, как это показано ниже: InStream = new DataInputStream( new BufferedInputStream( new FileInputStream("output.txt"))); Далее для этого потока создается разборщик, который оформлен в отдельном классе TokenizerOfStream, определенном в нашем приложении: TokenizerOfStream tos = new TokenizerOfStream(); Вслед за этим мы вызываем метод TokenizeIt, определенный в классе TokenizerOfStream, передавая ему в качестве параметра ссылку на входной поток: tos.TokenizeIt(InStream); Метод TokenizeIt выполняет разбор входного потока, отображая результаты разбора на консоли. После выполнения разбора входной поток закрывается методом close: InStream.close(); Самое интересное в нашем приложении связано, очевидно, с классом TokenizerOfStream, поэтому перейдем к его описанию. В этом классе определен только один метод TokenizeIt: public void TokenizeIt(InputStream is) { . . . } Получая в качестве параметра ссылку на входной поток, он прежде всего создает для него разборщик класса StreamTokenizer: StreamTokenizer stok; stok = new StreamTokenizer(is); Настройка параметров разборщика очень проста и сводится к вызовам всего двух методов: stok.slashSlashComments(true); stok.ordinaryChar('.'); Метод slashSlashComments включает режим распознавания комментариев в стиле языка программирования С++, а метод ordinaryChar объявляет символ ‘.’ обычным символом. После настройки запускается цикл разбора входного потока, причем условием завершения цикла является достижение конца этого потока: while(stok.nextToken() != StreamTokenizer.TT_EOF) { . . . } В цикле анализируется содержимое поля ttype, которое зависит от типа элемента, обнаруженного во входном потоке: switch(stok.ttype) { case StreamTokenizer.TT_WORD: { str = new String("\nTT_WORD >" + stok.sval); break; } case StreamTokenizer.TT_NUMBER: { str = "\nTT_NUMBER >" + Double.toString(stok.nval); break; } case StreamTokenizer.TT_EOL: { str = new String("> End of line"); break; } default: { if((char)stok.ttype == '"') str = new String("\nTT_WORD >" + stok.sval); else str = "> " + String.valueOf((char)stok.ttype); } } На слова и численные значения мы реагируем очень просто - записываем их текстовое представление в рабочую переменную str типа String. При обнаружении конца строки в эту переменную записывается строка End of line. Если же обнаружен обычный символ, мы сравниваем его с символом кавычки. При совпадении в переменную str записывается содержимое поля sval, в котором находятся слова, обнаруженные внутри кавычек. Если же обнаруженный символ не является символом кавычки, он преобразуется в строку и записывается в переменную str. В заключении метод выводит строку str в стандартный поток вывода, отображая на консоли выделенный элемент потока: System.out.println(str); Класс StringTokenizer Рассказывая о классе StreamTokenizer, нельзя не упомянуть о другом классе с похожим названием и назначением, а именно о классе StringTokenizer. Определение этого класса достаточно компактно, поэтому мы приведем его полностью: public class java.util.StringTokenizer extends java.lang.Object implements java.util.Enumeration { // --------------------------------------------------- // Конструкторы класса // --------------------------------------------------- public StringTokenizer(String str); public StringTokenizer(String str, String delim); public StringTokenizer(String str, String delim, boolean returnTokens); // --------------------------------------------------- // Методы // --------------------------------------------------- public String nextToken(); public String nextToken(String delim); public int countTokens(); public boolean hasMoreElements(); public boolean hasMoreTokens(); public Object nextElement(); } Класс StringTokenizer не имеет никакого отношения к потокам, так как предназначен для выделения отдельных элементов из строк типа String. Конструкторы класса получают в качетсве первого параметра str ссылку на разбираемую строку. Второй параметр delim, если он есть, задает разделители, с испльзованием которых в строке будут выделяться элементы. Параметр returnTokens определяет, надо ли вовзвращать обнаруженные разделители как элементы разбираемой строки. Рассмотрим кратко методы класса StringTokenizer. Для разбора строки приложение должно организовать цикл, вызывая в нем метод nextToken. Условием завершения цикла может быть либо возникновение исключения NoSuchElementException, либо возврат значения false методами hasMoreElements или hasMoreTokens. Метод countTokens позволяет определить, сколько раз был вызван метод nextToken перед возникновением исключения NoSuchElementException. Приложение StringToken Приложение StringToken получает одну строку из стандартного потока ввода и выполняет ее разбор с помощью класса StringTokenizer. Отдельные элементы строки выводятся на консоль в столбик (рис. 2.7). Рис. 2.7. Разбор строки в приложении StringToken В качестве разделителя заданы символы ",.; ", то есть запятая, точка, точка с запятой и пробел. Исходный текст приложения Исходный текст приложения StringToken вы найдете в листинге 2.6. Листинг 2.6. Файл StringToken\StringToken.java // ========================================================= // Разбор текстовой строки при помощи класса // StringTokenizer // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.util.*; // ========================================================= // Класс StringToken // Главный класс приложения // ========================================================= public class StringToken { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка, которая будет записана в поток String sOut; String str; try { // Выводим строку приглашения System.out.println("Enter string to parse..."); // Читаем с клавиатуры строку для записи в файл System.in.read(bKbdInput); // Преобразуем введенные символы в строку типа String sOut = new String(bKbdInput, 0); // Создаем разборщик текстовой строки StringTokenizer st; st = new StringTokenizer(sOut, ",.; "); // Запускаем цикл разборки строки while(st.hasMoreElements()) { // Получаем очередной жлемент str = new String((String)st.nextElement()); // Записываем его в стандартный поток вывода System.out.println(str); } System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста После ввода текстовой строки и ее записи в поле sOut наше приложение создает на базе этой строки объект st класса StringTokenizer: StringTokenizer st; st = new StringTokenizer(sOut, ",.; "); Далее он в цикле получает все элементы строки, вызывая для этого метод nextElement: while(st.hasMoreElements()) { str = new String((String)st.nextElement()); System.out.println(str); } Для проверки условия завершения цикла вызывается метод hasMoreElements. Когда он возвращает значение false, цикл завершается. Выделенные в цикле элементы строки записываются в переменную str и отображаются на консоли. Работа с файлами и каталогами при помощи класса File В предыдущих разделах мы рассмотрели классы, предназначенные для чтения и записи потоков. Однако часто возникает необходимость выполнения и таких операций, как определение атрибутов файла, создание или удаление каталогов, удаление файлов, получение списка всех файлов в каталоге и так далее. Для выполнения всех этих операций в приложениях Java используется класс с именем File. Создание объекта класса File У вас есть три возможности создать объект класса File, вызвав для этого один из трех конструкторов: public File(String path); public File(File dir, String name); public File(String path, String name); Первый из этих конструкторов имеет единственный параметр - ссылку на строку пути к файлу или каталогу. С помощью второго конструктора вы можете указать отдельно каталог dir и имя файла, для которого создается объект в текущем каталоге. И, наконец, третий конструктор позволяет указать полный путь к каталогу и имя файла. Если первому из перечисленных конструкторов передать ссылку со значением null, возникнет исключение NullPointerException. Пользоваться конструкторам очень просто. Вот, например, как создать объект класса File для файла c:\autoexec.bat и каталога d:\winnt: f1 = new File(“c:\\autoexec.bat”); f2 = new File(“d:\\winnt”); Определение атрибутов файлов и каталогов После того как вы создали объект класса File, нетрудно определить атрибуты этого объекта, воспользовавшись соответствующими методами класса File. Проверка существования файла или каталога С помощью метода exists вы можете проверить существование файла или катлога, для которого был создан объект класса File: public boolean exists(); Этот метод можно применять перед созданием потока на базе класса FileOutputStream, если вам нужно избежать случайной перезаписи существующего файла. В этом случае перед созданием выходного потока класса FileOutputStream следует создать объект класса File, указав конструктору путь к файлу, а затем проверить сущестование файла методом exists. Проверка возможности чтения и записи Методы canRead и canWrite позволяют проверить возможность чтения из файла и записи в файл, соответственно: public boolean canRead(); public boolean canWrite(); Их полезно применять перед созданием соответствующих потоков, если нужно избежать возникновение исключений, связанных с попыткой выполнения доступа неразрешенного типа. Если доступ разрешен, эти методы возвращают значение true, а если запрещен - false. Определение типа объекта - файл или каталог С помощью методов isDirectory и isFile вы можете проверить, чему соответствует созданный объект класса File - каталогу или файлу: public boolean isDirectory(); public boolean isFile(); Получение имени файла или каталога Метод getName возвращает имя файла или каталога для заданного объекта класса File (имя выделяется из пути): public String getName(); Получение абсолютного пути к каталогу Метод getAbsolutePath возвращает абсолютный путь к файлу или каталогу, который может быть машинно-зависимым: public String getAbsolutePath(); Определение типа указанного пути - абсолютный или относительный С помощью метода isAbsolute вы можете определить, соответствует ли данный объект класса File файлу или каталогу, заданному абсолютным (полным) путем, либо относительным путем: public boolean isAbsolute(); Определение пути к файлу или каталогу Метод getPath позволяет определить машинно-независимый путь файла или каталога: public String getPath(); Определение родительского каталога Если вам нужно определить родительский каталог для объекта класса File, то это можно сделать методом getParent: public String getParent(); Определение длины файла в байтах Длину файла в байтах можно определить с помощью метода length: public long length(); Определение времени последней модификации файла или каталога Для определения времени последней модификации файла или каталога вы можете вызвать метод lastModified: public long lastModified(); Заметим, однако, что этот метод возвращает время в относительных единицах с момента запуска системы, поэтому его удобно использовать только для относительных сравнений. Получение текстового представления объекта Метод toString возвращает текстовую строку, представляющую объект класса File: public String toString(); Получение значения хэш-кода Метод hashCode возвращает значение хэш-кода, соответствующего объекту File: public int hashCode(); Удаление файлов и каталогов Для удаления ненужного файла или каталога вы должны создать соответствующий объект File и затем вызвать метод delete: public boolean delete(); Создание каталогов С помощью методов mkdir и mkdirs можно создавать новые каталоги: public boolean mkdir(); public boolean mkdirs(); Первый из этих методов создает один каталог, второй - все подкаталоги, ведущие к создаваемому каталогу (то есть полный путь). Переименование файлов и каталогов Для переименования файла или каталога вы должны создать два объекта класса File, один из которых соответствует старому имени, а второй - новому. Затем для перовго из этих объектов нужно вызвать метод renameTo, указав ему в качестве параметра ссылку на второй объект: public boolean renameTo(File dest); В случае успеха метод возвращает значение true, при возникновении ошибки - false. Может также возникать исключение SecurityException. Сравнение объектов класса File Для сравнения объектов класса File вы должны использовать метод equals: public boolean equals(Object obj); Заметим, что этот метод сравнивает пути к файлам и каталогам, но не сами файли или каталоги. Получение списка содержимого каталога С помощью метода list вы можете получить список содержимого каталога, соответствующего данному объекту класса File. В классе File предусмотрено два варианта этого метода - без параметра и с параметром: public String[] list(); public String[] list(FilenameFilter filter); Первый из этих методв возвращает массив строк с именами содержимого каталога, не включая текущий каталог и родительский каталог. Второй позволяет получить список не всех объектов, хранящихся в каталоге, а только тех, что удовлетворяют условиям, определенным в фильтре filter класса FilenameFilter. Пример приложения, которое просматривает содержимое каталога и использует при этом фильтр, вы найдете ниже в разделе “Приложение DirList”. Приложение FileInfo В приложении FileInfo мы демонстрируем способы работы с классом File. После запуска наше приложение предлагает ввести путь к файлу (рис. 2.8). Вы также можете ввести путь к каталогу. Рис. 2.8. Работа приложения FileInfo Далее приложение создает объект класса File, передавая введенную строку соответствующему конструктору, а затем, если указанный файл или каталог существует, отображает его параметры. На рис. 2.8 видно, что в ответ на прглашение был введен путь к файлу autoexec.bat. Приложение вывело родительский каталог, в котором находится этот файл (диск c:), длину файла в байтах (235 байт), а также сообщило нам, что для файла разрешены операции чтения и записи. Исходный текст приложения FileInfo Исходный текст приложения FileInfo приведен в листинге 2.7. Листинг 2.7. Файл FileInfo\FileInfo.java // ========================================================= // Просмотр атрибутов файла при помощи класса File // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.util.*; // ========================================================= // Класс FileInfo // Главный класс приложения // ========================================================= public class FileInfo { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка String sFilePath; try { // Выводим строку приглашения System.out.println("Enter file path..."); // Читаем с клавиатуры строку для записи в файл System.in.read(bKbdInput); // Преобразуем введенные символы в строку типа String sFilePath= new String(bKbdInput, 0); // Отбрасываем символ конца строки StringTokenizer st; st = new StringTokenizer(sFilePath, "\r\n"); sFilePath = new String((String)st.nextElement()); // Создаем объект класса File, соответствующий // введенному пути File fl = new File(sFilePath); // Если указанный файл или каталог не существует, // выводим сообщение и завершаем работу if(!fl.exists()) { System.out.println("File not found: " + sFilePath); } // Если путь существует, определяем параметры // соответствующего файла или каталога else { // Проверяем, был указан файл или каталог if(fl.isDirectory()) System.out.println("File " + sFilePath + " is directory"); else if (fl.isFile()) System.out.println("File " + sFilePath + " is file"); // Получаем и выводим атрибуты файла или каталога System.out.println( "Parent: " + fl.getParent() + "\nLength: " + fl.length() + "\nRead op. available: " + fl.canRead() + "\nWrite op. available: " + fl.canWrite()); } System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста После ввода с клавиатуры пути к файлу или каталогу приложение записывает введенный путь в строку sFilePath класса String. Так как в этой строке имеется символ конца строки, нам нужно его отрезать. Для этого мы воспользуемся классом StringTokenizer, задав для него в качестве разделителя символ конца строки: StringTokenizer st; st = new StringTokenizer(sFilePath, "\r\n"); sFilePath = new String((String)st.nextElement()); Первый же вызов метода nextElement возвращает нам строку пути, которую мы и сохраняем в поле sFilePath. Далее мы создаем объект класса File, передавая конструктору этого класса строку sFilePath: File fl = new File(sFilePath); Так как при вводе пути файла или каталога вы можете допустить ошибку, приложение, прежде чем продолжать свою работу, проверяет существование указанного файла или каталога. Для проверки вызывается метод exists, определенный в классе File: if(!fl.exists()) { System.out.println("File not found: " + sFilePath); } На следующем этапе приложение проверяет, является ли объект класса File каталогом, вызвая метод isDirectory: if(fl.isDirectory()) System.out.println("File " + sFilePath + " is directory"); Аналогичная проверка выполняется методом isFile на принадлежность объекта к файлам: else if (fl.isFile()) System.out.println("File " + sFilePath + " is file"); На последнем этапе приложение определяет различные атрибуты файла или каталога, вызывая соответствующие методы класса File: System.out.println( "Parent: " + fl.getParent() + "\nLength: " + fl.length() + "\nRead op. available: " + fl.canRead() + "\nWrite op. available: " + fl.canWrite()); Параметры отображаются на консоли методом println. Приложение DirList В приложении DirList мы используем класс File для получения списка всех файлов и каталогов, расположенных в заданном каталоге. После запуска приложение DirList предлагает ввести путь к каталогу и маску для имени файла (рис. 2.9). Рис. 2.9. Работа приложения DirList Если вместо маски задать символ ‘*’, как мы сделали это на рис. 2.9, приложение выведет полный список файлов и каталогов, выделив каталоги прописными буквами. В том случае, если будет задна другая маска, в окне появятся только такие файлы, которые содержат эту маску как подстроку (рис. 2. 10). Рис. 2.10. Просмотр содержимого каталога c:\dos с маской com Исходный текст приложения Исходный текст приложения DirList представлен в листинге 2.8. Листинг 2.8. Файл DirList\DirList.java // ========================================================= // Просмотр содержимого каталога при помощи класса File // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.util.*; // ========================================================= // Класс DirList // Главный класс приложения // ========================================================= public class DirList { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Путь к каталогу, содержимое которого // мы будем просматривать String sDirPath; // Маска для просмотра String sMask; // Массив строк содержимого каталога String[] dirlist; try { // Выводим строку приглашения для ввода пути // к каталогу, содержимое которого будем просматривать System.out.println("Enter directory path..."); System.in.read(bKbdInput); sDirPath = new String(bKbdInput, 0); StringTokenizer st; st = new StringTokenizer(sDirPath, "\r\n"); sDirPath = new String((String)st.nextElement()); // Вводим строку маски System.out.println("Enter mask..."); System.in.read(bKbdInput); sMask = new String(bKbdInput, 0); st = new StringTokenizer(sMask, "\r\n"); sMask = new String((String)st.nextElement()); // Создаем объект класса File, соответствующий // введенному пути File fdir = new File(sDirPath); // Если указанный каталог не существует, // выводим сообщение и завершаем работу if(!fdir.exists()) { System.out.println("Directory not found: " + sDirPath); } // Если путь существует, определяем параметры // соответствующего файла или каталога else { // Проверяем, был указан файл или каталог if(!fdir.isDirectory()) System.out.println("File " + sDirPath + " is not directory"); // Если указан каталог, получаем его содержимое else { // Если маска не задана, вызываем метод list // без параметров if(sMask == null) dirlist = fdir.list(); // Вызываем метод list, создавая фильтр else dirlist = fdir.list(new MaskFilter(sMask)); // Сканируем полученный массив for (int i = 0; i < dirlist.length; i++) { // Для каждого объекта, обнаруженного в // каталоге, создаем объект класса File File f = new File(sDirPath + "\\" + dirlist[i]); // Имена файлов отображаем строчными буквами if(f.isFile()) System.out.println(dirlist[i].toLowerCase()); // Имена каталогов оставляем без изменения else System.out.println(dirlist[i]); } } } System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } // ========================================================= // Класс MaskFilter // Фильтр для просмотра каталога // ========================================================= class MaskFilter implements FilenameFilter { // Поле для хранения маски имени String sNameMask; // ------------------------------------------------------- // Конструктор класса MaskFilter // Сохраняет маску фильтра // ------------------------------------------------------- MaskFilter(String sMask) { // Записываем маску прописными буквами sNameMask = sMask.toUpperCase(); } // ------------------------------------------------------- // Метод accept // Проверка имени по маске // ------------------------------------------------------- public boolean accept(File dir, String name) { // Если маска указана как символ *, подходит любое имя if(sNameMask.equals("*")) return true; // Если имя содержит маску, возвращаем значение true return (name.indexOf(sNameMask) != -1); } } Описание исходного текста В начале своей работы приложение вводит с клавиатуры путь к каталогу и отрезает из полученной строки символ новой строки, пользуясь для этого классом StringTokenizer: System.out.println("Enter directory path..."); System.in.read(bKbdInput); sDirPath = new String(bKbdInput, 0); StringTokenizer st; st = new StringTokenizer(sDirPath, "\r\n"); sDirPath = new String((String)st.nextElement()); Строка пути записывается в поле sDirPath. Аналогичным образом вводится и обрабатывается маска, которая записывается в поле sMask. Далее создается объект класса File, соответствующий каталогу sDirPath, содержимое которого нужно просмотреть: File fdir = new File(sDirPath); После этого выполняется проверка существования пути, а также проверка, указывает ли этот путь на каталог. Для проверки мы применяем методы exists и isDirectory, рассмотренные ранее. Если все нормально, и был указан существующий каталог, приложение анализирует поле маски sMask. В случае пустой маски для получения содержимого каталога мы вызваем метод list без параметров: if(sMask == null) dirlist = fdir.list(); Если же маска определена, вызывается второй вариант этого же метода: else dirlist = fdir.list(new MaskFilter(sMask)); Здесь в качестве параметра методу list мы передаем вновь созданный объект класса MaskFilter (фильтр), передав соответствующему конструктору строку маски. В любом случае метод list заполняет полученным списком массив строк dirlist. Содержимое этого массива перебирается в цикле: for (int i = 0; i < dirlist.length; i++) { File f = new File(sDirPath + "\\" + dirlist[i]); if(f.isFile()) System.out.println(dirlist[i].toLowerCase()); else System.out.println(dirlist[i]); } Для каждого элемента массива мы создаем объект класса File, передавая конструктору путь каталога, добавив к нему разделитель и строку элемента массива. Затем если данная строка соответсвует файлу, а не каталогу, имя выводится строчными буквами. Для преобразования мы вызываем метод toLowerCase, определенный в классе String. Рассмотрим теперь класс MaskFilter, предназначенный для фильтрации имен, которые метод list возвращает вызвавшему его методу. Класс MaskFilter определен следующим образом: class MaskFilter implements FilenameFilter { . . . } Как видно из определения, этот класс реализует интерфейс FilenameFilter. В рамках интерфейса FilenameFilter вам нужно переопределить метод accept, который проверяет, подходит ли имя файла критерию отбора, заданному маской, и в зависимости от этого возвращает либо значение true (если подходит), либо false (если не подходит). Маска передается конструктору класса MaskFilter, преобразуется им в прописные буквы и сохраняется в поле sNameMask для использования в процессе проверки методом accept: MaskFilter(String sMask) { sNameMask = sMask.toUpperCase(); } Что же касается метода accept, то он выглядит достаточно просто: public boolean accept(File dir, String name) { if(sNameMask.equals("*")) return true; return (name.indexOf(sNameMask) != -1); } В качестве первого параметра этому методу передается путь к каталогу, а в качестве второго - имя файла. Метод accept вызывается для каждого файла и каталога, расположенного в каталоге dir. Наша реализация этого метода вначале проверяет маску. Если маска задана как строка “*”, подходит любое имя, поэтому метод accept всегда возвращает значение true. Если же используются другие маски, то наш метод выполняет ее поиск в строке имени с помощью метода indexOf. Если строка маски найдена как подстрока имени файла или каталога, такое имя нам подходит и метод accept возвращает значение true. В противном случае возвращается значение false. Произвольный доступ к файлам В ряде случаев, например, при создании системы управления базой данных, требуется обеспечить произвольный доступ к файлу. Рассмотренные нами ранее потоки ввода и вывода пригодны лишь для последовательного доступа, так как в соответствующих классах нет средств позиционирования внутри файла. Между тем библиотека классов Java содержит класс RandomAccessFile, который предназначен специально для организации прямого доступа к файлам как для чтения, так и для записи. В классе RandomAccessFile определено два конструктора, прототипы которых показаны ниже: public RandomAccessFile(String name, String mode); public RandomAccessFile(File file, String mode); Первый из них позволяет указывать имя файла, и режим mode, в котором открывается файл. Второй конструктор вместо имени предполагает использование объекта класса File. Если файл открывается только для чтения, вы должны передать конструктору текстовую строку режима "r". Если же файл открывается и для чтения, и для записи, конструктору передается строка "rw". Позиционирование внутри файла обеспечивается методом seek, в качестве параметра pos которому передается абсолютное смещение файла: public void seek(long pos); После вызова этого метода текущая позиция в файле устанавливается в соответствии со значением параметра pos. В любой момент времени вы можете определить текущую позицию внутри файла, вызвав метод getFilePointer: public long getFilePointer(); Еще один метод, который имеет отношение к позиционированию, называется skipBytes: public int skipBytes(int n); Он работает так же, как и одноименный метод для потоков - продвигает текущую позицию в файле на заданное количество байт. С помощью метода close вы должны закрывать файл, после того как работа с им завершена: public void close(); Метод getFD позволяет получить дескриптор файла: public final FileDescriptor getFD(); С помощью метода length вы можете определить текущую длину файла: public long length(); Ряд методов предназначен для выполнения как обычного, так и форматированного ввода из файла. Этот набор аналогичен методам, определенным для потоков: public int read(); public int read(byte b[]); public int read(byte b[], int off, int len); public final boolean readBoolean(); public final byte readByte(); public final char readChar(); public final double readDouble(); public final float readFloat(); public final void readFully(byte b[]); public final void readFully(byte b[], int off, int len); public final int readInt(); public final String readLine(); public final long readLong(); public final short readShort(); public final int readUnsignedByte(); public final int readUnsignedShort(); public final String readUTF(); Существуют также методы, позволяющие выполнять обычную или форматированную запись в файл с прямым доступом: public void write(byte b[]); public void write(byte b[], int off, int len); public void write(int b); public final void writeBoolean(boolean v); public final void writeByte(int v); public final void writeBytes(String s); public final void writeChar(int v); public final void writeChars(String s); public final void writeDouble(double v); public final void writeFloat(float v); public final void writeInt(int v); public final void writeLong(long v); public final void writeShort(int v); public final void writeUTF(String str); Имена приведенных методов говорят сами за себя, поэтому мы не будем их описывать. Приложение DirectFileAccess Для иллюстрации способов работы с классом RandomAccessFile мы подготовили приложение DirectFileAccess, в котором создается небольшая база данных. Эта база данных состоит из двух файлов: файла данных и файла индекса. В файле данных хранятся записи, сосотящие из двух полей - текстового и числового. Текстовое поле с названием name хранит строки, закрытые смиволами конца строки “\r\n”, а числовое с названием account - значения типа int. Дамп файла данных, создаваемого при первом запуске приложения DirectFileAccess, приведен на рис. 2.11. Рис. 2.11. Дамп файла данных Из этого дампа видно, что после первого запуска приложения в файле данных имеются следующие записи: Номер записиСмещение в файле данныхПоле nameПоле account00Ivanov1000112Petrov 2000224Sidoroff3000 При последующих запусках каждый раз в файл данных будут добавляться приведенные выше записи. Так как поле name имеет переменную длину, для обеспечения возможности прямого доступа к записи по ее номеру необходимо где-то хранить смещения всех записей. Мы это делаем в файле индексов, дамп которого на момент после первого запуска приложения представлен на рис. 2.12. Рис. 2.12. Дамп файла индекса Файл индексов хранит 8-байтовые смещения записей файла данных в формате long. Зная номер записи, можнор легко вычислить смещение в файле индексов, по которому хранится смещение нужной записи в файле данных. Если извлечь это смещение, то можно выполнить позиционирование в файле данных с целью чтения нужной записи, что и делает наше приложение. После добавления трех записей в базу данных приложение извлекает три записи в обратном порядке, то есть сначала запись с номером 2, затем с номером 1, и, наконец, с номером 0. Извлеченные записи отображаются в консольном окне приложения (рис. 2.13). Рис. 2.13. Отображение записей базы данных приложением DirectFileAccess Исходные тексты приложения Исходные тексты приложения DirectFileAccess приведены в листинге 2.9. Листинг 2.9. Файл DirectFileAccess\DirectFileAccess.java // ========================================================= // Прямой доступ к файлу с помощью класса RandomAccessFile // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.util.*; // ========================================================= // Класс DirectFileAccess // Главный класс приложения // ========================================================= public class DirectFileAccess { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; try { // Создаем новую базу данных SimpleDBMS db = new SimpleDBMS( "dbtest.idx", "dbtest.dat"); // Добавляем в нее три записи db.AddRecord("Ivanov", 1000); db.AddRecord("Petrov", 2000); db.AddRecord("Sidoroff", 3000); // Получаем и отображаем содержимое первых трез // записей с номерами 2, 1 и 0 System.out.println(db.GetRecordByNumber(2)); System.out.println(db.GetRecordByNumber(1)); System.out.println(db.GetRecordByNumber(0)); // Закрываем базу данных db.close(); // После ввода любой строки завершаем работу программы System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } // ========================================================= // Класс SimpleDBMS // Простейшая база данных // ========================================================= class SimpleDBMS { // Файл индексов RandomAccessFile idx; // Файл данных RandomAccessFile dat; // Значение указателя на текущую запись long idxFilePointer = 0; // ------------------------------------------------------- // SimpleDBMS // Конструктор. Создает и открывает файлы базы данных // ------------------------------------------------------- public SimpleDBMS(String IndexFile, String DataFile) { try { // Создаем и открываем файл индексов idx = new RandomAccessFile(IndexFile, "rw"); // Создаем и открываем файл данных dat = new RandomAccessFile(DataFile, "rw"); } catch(Exception ioe) { System.out.println(ioe.toString()); } } // ------------------------------------------------------- // close // Метод close. Закрывает файлы базы данных // ------------------------------------------------------- public void close() { try { // Закрываем файл индексов idx.close(); // Закрываем файл данных dat.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } } // ------------------------------------------------------- // AddRecord // Добавление записи в базу данных // ------------------------------------------------------- public void AddRecord(String name, int account) { try { // Устанавливаем текущую позицию в файлах // индекса и данных на конец файла idx.seek(idx.length()); dat.seek(dat.length()); // Получаем смещение в файле данных места, // куда будет добавлена новая запись idxFilePointer = dat.getFilePointer(); // Сохраняем это смещение в файле индексов idx.writeLong(idxFilePointer); // Сохраняем в файле дайнных два поля новой записи dat.writeBytes(name + "\r\n"); dat.writeInt(account); } catch(Exception ioe) { System.out.println(ioe.toString()); } } // ------------------------------------------------------- // GetRecordByNumber // Извлечение записи по ее порядковому номеру // ------------------------------------------------------- public String GetRecordByNumber(long nRec) { // Строка, в которой будет сохранена извлеченная запись String sRecord = ""; try { // Значение поля account Integer account; // Значение поля name String str = null; // Вычисляем смещение в файле индексов по порядковому // номеру записи idx.seek(nRec * 8); // Извлекаем из файла индексов смещение записи // в файле данных idxFilePointer = idx.readLong(); // Выполняем позиционирование на нужную запись // в файле данных dat.seek(idxFilePointer); // Извлекаем поля записи str = dat.readLine(); account = new Integer(dat.readInt()); // Объединяем значения полей в текстовую строку sRecord = new String("> " + account + ", " + str); } catch(Exception ioe) { System.out.println(ioe.toString()); } // Возвращаем извлеченную запись return sRecord; } } Описание исходных текстов Для работы с базой данных мы создали класс SimpleDBMS, определив в нем конструктор, методы для добавления записей, извлечения записей по их порядковому номеру, а также метод для закрытия базы данных. Метод main Сразу после запуска метод main приложения DirectFileAccess создает базу данных, передавая конструктору имена файла индекса dbtest.idx и файла данных dbtest.dat: SimpleDBMS db = new SimpleDBMS("dbtest.idx", "dbtest.dat"); После этого с помощью метода AddRecord, определенного в классе SimpleDBMS, в базу добавляются три записи, состоящие из текстового и числового полей: db.AddRecord("Ivanov", 1000); db.AddRecord("Petrov", 2000); db.AddRecord("Sidoroff", 3000); Сразу после добавления записей приложение извлекает три записи с номерами 2, 1 и 0, вызывая для этого метод GetRecordByNumber, также определенный в классе SimpleDBMS: System.out.println(db.GetRecordByNumber(2)); System.out.println(db.GetRecordByNumber(1)); System.out.println(db.GetRecordByNumber(0)); Извлеченные записи отображаются на системной консоли. После завершения работы с базой данных она закрывается методом close из класса SimpleDBMS: db.close(); Класс SimpleDBMS Рассмотрим теперь класс SimpleDBMS. В этом классе определено три поля с именами idx, dat и idxFilePointer, а также три метода. Поля класса SimpleDBMS Поля idx dat являются объектами класса RandomAccessFile и представляют собой, соответственно, ссылки на файл индекса и файл данных. Поле idxFilePointer типа long используется как рабочее и хранит текущее смещение в файле. Конструктор класса SimpleDBMS Конструктор класса SimpleDBMS выглядит достаточно просто. Все, что он делает, - это создает два объекта класса RandomAccessFile, соответственно, для индекса и данных: idx = new RandomAccessFile(IndexFile, "rw"); dat = new RandomAccessFile(DataFile, "rw"); Так как в качестве второго параметра конструктору класа RandomAccessFile передается строка "rw", файлы открываются и для чтения, и для записи. Метод close Метод close закрывает файлы индекса и данных, вызывая метод close из класса RandomAccessFile: idx.close(); dat.close(); Метод AddRecord Метод AddRecord добавляет новую запись в конец файла данных, а смещение этой записи - в конец файла индекса. Поэтому перед началом своей работы текущая позиция обоих указанных файлов устанавливается на конец файла. Для установки мы применили метод seek из класса RandomAccessFile, передав ему в качестве параметра значение длины файла в байтах, определенное при помощи метода length из того же класса: idx.seek(idx.length()); dat.seek(dat.length()); Перед тем как добавлять новую запись в файл данных, метод AddRecord определяет текущую позицию в файле данных (в данном случае это позиция конца файла) и записывает эту позицию в файл индекса: idxFilePointer = dat.getFilePointer(); idx.writeLong(idxFilePointer); Далее метод AddRecord выполняет сохранение полей записи в файле данных. Для записи строки вызывается метод writeBytes, а для записи численного значения типа int - метод writeInt: dat.writeBytes(name + "\r\n"); dat.writeInt(account); Обратите внимение, что к строке мы добавляем символы возврата каретки и перевода строки. Это сделано исключительно для того чтобы обозначить конец строки текстового поля. Метод GetRecordByNumber Метод GetRecordByNumber позволяет извлечь произвольную запись из файла данных по ее порядковому номеру. Напомним, что смещения всех записей хранятся в файле индексов и имеют одинаковую длину 8 байт. Пользуясь этим, метод GetRecordByNumber вычисляет смещение в файле индекса простым умножением порядкового номера записи на длину переменной типа long, то есть на 8 байт, а затем выполняет позиционирование: idx.seek(nRec * 8); После этого метод GetRecordByNumber извлекает из файла индексов смещение нужной записи в файле данных, вызывая для этого метод readLong, а затем выполняет позиционирование в файле данных: idxFilePointer = idx.readLong(); dat.seek(idxFilePointer); Поля записи читаются из файла данных в два приема. Вначале читается строка текстового поля, а затем - численное значение, для чего вызываются, соответственно, методы readLine и readInt: str = dat.readLine(); account = new Integer(dat.readInt()); Полученные значения полей объединяются в текстовой строке и записываются в переменную sRecord: sRecord = new String("> " + account + ", " + str); Содержимое этой переменной метод GetRecordByNumber возвращает в качестве извлеченной строки записи базы данных. 3 СОЗДАНИЕ СЕТЕВЫХ ПРИЛОЖЕНИЙ Когда в 30 томе “Библиотеки системного программиста” мы начинали разговор про язык программирования Java, то отмечали, что он специально ориентирован на глобальные сети, такие как Internet. В этой главе мы начнем знакомство с конкретными классами Java, разработанными для сетевого программирования. На примере наших приложений вы сможете убедиться, что классы Java действительно очень удобны для создания сетевых приложений. В этой главе мы рассмотрим два аспекта сетевого программирования. Первый из них касается доступа из приложений Java к файлам, расположенным на сервере Web, второй - создания серверных и клиентских приложений с использованием сокетов. Напомним, что из соображений безопасности алпетам полностью запрещен доступ к локальным файлам рабочей станции, подключенной к сети. Тем не менее, аплет может работать с файлами, расположенными на серверах Web. При этом можно использовать входные и выходные потоки, описанные нами в предыдущей главе. Для чего аплетам обращаться к файлам сервера Web? Таким аплетам можно найти множество применений. Представьте себе, например, что вам нужно отображать у пользователя диаграмму, исходные данные для построения которой находятся на сервере Web. Эту задачу можно решить, грубо говоря, двумя способами. Первый заключается в том, что вы создаете расширение сервера Web в виде приложения CGI или ISAPI, которое на основании исходных данных динамически формирует графическое изображение диаграммы в виде файла GIF и посылает его пользователю. Что касается расширения сервера Web, то вы сможете его создать, пользуясь 29 томом “Библиотеки системного программиста”, который называется “Сервер Web своими руками”. Однако на пути решения задачи с помощью расширения сервера Web вас поджидают две неприятности. Во-первых, создать из программы красивый цветной графический файл в стандарте GIF не так-то просто - вы должны разобраться с форматом этого файла и создать все необходимые заголовки. Во-вторых, графический файл занимает много места и передается по каналам Internet достаточно медленно - средняя скорость передачи данных в Internet составляет 1 Кбайт в секунду. В то же время файл с исходными данными может быть очень компактным. Возникает вопрос - нельзя ли передавать через Internet только исходные данные, а построение графической диаграммы выполнять на рабочей станции пользователя? В этом заключается второй способ, который предполагает применение аплетов. Ниже мы приведем исходные тексты соотвестсвующего аплета в разделе “Приложение ShowChart”. Это приложение получает через сеть файл исходных данных, а затем на основании содержимого этого файла рисует в своем окне цветную круговую диаграмму. Объем передаваемых данных при этом по сравнению с использованием расширения сервера Web сокращается десятки раз. Помимо работы с файлами, расположенными на сервере Web, в этой главе мы расскажем о создании каналов между приложениями Java, работающими на различных компьютерах в сети, с использованием сокетов. Сокеты позволяют организовать тесное взаимодействие аплетов и полноценных приложений Java, при котором аплеты могут предавать друг другу данные через сеть Internet. Это открывает широкие возможности для обработки информации по схеме клиент-сервер, причем в роли серверов здесь может выступать любой компьютер, подключенный к сети, а не только сервер Web. Каждая рабочая станция может выступать одновременно и в роли сервера, и в роли клиента. Адрес IP и класс InetAddress Прежде чем начинать создание сетевых приложений для Internet, вы должны разобраться с адресацией компьютеров в сети с протоколом TCP/IP, на базе которого построена сеть Internet. Подробную информацию об адресации вы можете получить из только что упомянутого 29 тома “Библиотеки системного программиста”. Здесь мы приведем только самые необходимые сведения. Все компьютеры, подключенные к сети TCP/IP, называются узлами (в оригинальной терминологии узел - это host). Каждый узел имеет в сети свой адрес IP, состоящий из четырех десятичных цифр в диапазоне от 0 до 255, разделенных символом “точка “, например: 193.120.54.200 Фактически адрес IP является 32-разрядным двоичным числом. Упомянутые числа представляют собой отдельные байты адеса IP. Так как работать с цифрами удобно лишь компьютеру, была придумана система доменных имен. При использовании этой системы адресам IP ставится в соответсвие так называемый доменный адрес, такой как www.microsoft.com. В сети Internet имеется распределенная по всему миру база доменных имен, в которой установлено соответствие между доменными именами и адресами IP в виде четырех чисел. Для работы с адресами IP в библиотеке классов Java имеется класс InetAddress, определение наиболее интересных методов которого приведено ниже: public static InetAddress getLocalHost(); public static InetAddress getByName(String host); public static InetAddress[] getAllByName(String host); public byte[] getAddress(); public String toString(); public String getHostName(); public boolean equals(Object obj); Рассмотрим применение этих методов. Прежде всего вы должны создать объект класса InetAddress. Эта процедура выполняется не с помощью оператора new, а с применением статических методов getLocalHost, getByName и getAllByName. Создание объекта класса InetAddress для локального узла Метод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java. Так как этот метод статический, вы можете вызывать его, ссылаясь на имя класса InetAddress: InetAddress iaLocal; iaLocal = InetAddress.getLocalHost(); Создание объекта класса InetAddress для удаленного узла В том случае, если вас интересует удаленный узел сети Internet или корпоративной сети Intranet, вы можете создать для него объект класса InetAddress с помощью методов getByName или getAllByName. Первый из них возвращает адрес узла, а второй - массив всех адресов IP, связанных с данным узлом. Если узел с указанным именем не существует, при выполнении методов getByName и getAllByName возникает исключение UnknownHostException. Заметим, что методам getByName и getAllByName можно передавать не только имя узла, такое как “microsoft.com”, но и строку адреса IP в виде четырех десятичных чисел, разделенных точками. После создания объекта класса InetAddress для локального или удаленного узла вы можете использовать другие методы этого класса. Определение адреса IP Метод getAddress возвращает массив из чеырех байт адреса IP объекта. Байт с нулевым индексом этого массива содержит старший байт адреса IP. Метод toString возвращает текстовую строку, которая содержит имя узла, разделитель ‘/’ и адрес IP в виде четырех десятичных чисел, разделенных точками. Определение имени узла С помощью метода getHostName вы можете определить имя узла, для которого был создан объект класса InetAddress. Сравнение адресов IP И, наконец, метод equals предназначен для сравнения адресов IP как объектов класса InetAddress. Приложение InetAddressDemo Приложение InetAddressDemo отображает имя и адрес IP локального узла, а затем запрашивает имя удаленного узла. Еси такой узел существует, для него определяется и отображается на консоли список адресов IP (рис. 3.1). Рис. 3.1. Работа приложения InetAddressDemo Если же указано имя несуществующего узла, возникает исключение UnknownHostException, о чем на консоль выводится сообщение. Исходные тексты приложения InetAddressDemo Исходные тексты приложения InetAddressDemo приведены в листинге 3.1. Листинг 3.1. Файл InetAddressDemo\InetAddressDemo.java // ========================================================= // Работа с адресами IP с помощью класса InetAddress // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.net.*; import java.io.*; import java.util.*; // ========================================================= // Класс InetAddressDemo // Главный класс приложения // ========================================================= public class InetAddressDemo { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка String sIn; // Рабочая строка String str; // Адрес локального узла InetAddress iaLocal; // Массив байт адреса локального узла byte[] iaLocalIP; // Массив всех адресов удаленного узла InetAddress[] iaRemoteAll; try { // Получаем адрес локального узла iaLocal = InetAddress.getLocalHost(); // Отображаем имя локального узла на консоли System.out.println("Local host name: " + iaLocal.getHostName()); // Определяем адрес IP локального узла iaLocalIP = iaLocal.getAddress(); // Отображаем отдельные байты адреса IP // локального узла System.out.println("Local host IP address: " + (0xff & (int)iaLocalIP[0]) + "." + (0xff & (int)iaLocalIP[1]) + "." + (0xff & (int)iaLocalIP[2]) + "." + (0xff & (int)iaLocalIP[3])); // Отображаем адрес IP локального узла, полученный // в виде текстовой строки System.out.println("Local host IP address: " + iaLocal.toString()); // Вводим имя удаленного узла, адрес которого // мы будет определять System.out.println("Enter remote host name..."); System.in.read(bKbdInput); sIn = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(sIn, "\r\n"); str = new String((String)st.nextElement()); // Получаем все адреса IP, свяжанные с удаленным // узлом, имя которого мы только что ввели iaRemoteAll = InetAddress.getAllByName(str); // Отображаем эти адреса на консоли for(int i = 0; i < iaRemoteAll.length; i++) { System.out.println("Remote host IP address: " + iaRemoteAll[i].toString()); } System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходных текстов Сразу после запуска приложение создает кобъект класса InetAddress для локального узла, вызывая для этого статический метод getLocalHost: iaLocal = InetAddress.getLocalHost(); Далее для созданного объекта вызывается метод getHostName, возвращающий строку имени локального узла: System.out.println("Local host name: " + iaLocal.getHostName()); Это имя отображается на консоли приложения. Затем приложение определяет адрес IP локального узла, вызывая метод getAddress: iaLocalIP = iaLocal.getAddress(); Напомним, что этот метод возвращает массив четырех байт адреса IP. Адрес IP мы отображаем на консоли с помощью метода println: System.out.println("Local host IP address: " + (0xff & (int)iaLocalIP[0]) + "." + (0xff & (int)iaLocalIP[1]) + "." + (0xff & (int)iaLocalIP[2]) + "." + (0xff & (int)iaLocalIP[3])); Заметьте, что байты адреса записваются в массив типа byte как знаковые величины. Для того чтобы отображить их на консоли в виде положительных чисел, мы вначале выполняем явное преобразование к типу int, а затем обнуляем старший байт (так как такое преобразование выполняется с сохранением знака). Наше приложение демонстрирует также другой способ получения адреса IP для объекта класса InetAddress, который заключается в вызове метода toString: System.out.println("Local host IP address: " + iaLocal.toString()); На втором этапе приложение InetAddressDemo вводит строку имени удаленного узла и, после удаления символа перехода на новую строку, пытается создать для введенного имени массив объектов класса InetAddress. Для этого приложение вызывает метод getAllByName: iaRemoteAll = InetAddress.getAllByName(str); Содержимое созданного массива отображается в цикле, причем адрес IP извлекается из объектов класса InetAddress методом toString: for(int i = 0; i < iaRemoteAll.length; i++) { System.out.println("Remote host IP address: " + iaRemoteAll[i].toString()); } Универсальный адрес ресурсов URL Адрес IP позволяет идентифицировать узел, однако его недостаточно для идентификации ресурсов, имеющихся на этом узле, таких как работающие приложения или файлы. Причина очевидна - на узле, имеющем один адрес IP, может существовать много различных ресурсов. Для ссылки на ресурсы сети Internet применяется так называемый универсальный адрес ресуросв URL (Universal Resource Locator). В общем виде этот адрес выглядит следующим образом: [protocol]://host[:port][path] Строка адреса начинаетс с протокола protocol, который должен быть использован для доступа к ресурсу. Документы HTML, например, передаются из сервера Web удаленным пользователям с помощью протокола HTTP. Файловые серверы в сети Internet работают с протоколом FTP. Для ссылки на сетевые ресурсы через протокол HTTP используется следующая форма универсального адреса ресурсов URL: http://host[:port][path] Параметр host обязательный. Он должен быть указан как доменный адрес или как адрес IP (в виде четырех десятичных чисел). Например: http://www.microsoft.com http://154.23.12.101 Необязательный параметр port задает номер порта для работы с сервером. По умолчанию для протокола HTTP используется порт с номером 80, однако для специализированных серверов Web это может быть и не так. Номер порта идентифицирует программу, работающую в узле сети TCP/IP и взаимодействующую с другими программами, расположенными на том же или на другом узле сети. Если вы разрабатываете программу, передающую данные через сеть TCP/IP с использованием, например, интерфейса сокетов, то при создании канала связи с уделенным компьютером вы должны указать не только адрес IP, но и номер порта, который будет использован для передачи данных. Ниже мы показали, как нужно указывать в адресе URL номер порта: http://www.myspecial.srv/:82 Теперь займемся параметром path, определяющем путь к объекту. Обычно любой сервер Web или FTP имеет корневой каталог, в котором расположены подкаталоги. Как в корневом каталоге, так и в подкаталогах сервера Web могут находиться документы HTML, двоичные файлы, файлы с графическими изображениями, звуковые и видео-файлы, расширения сервера в виде программ CGI или библиотек динамической компоновки, дополняющих возможности сервера (такие, как библиотеки ISAPI для сервера Microsoft Information Server). Если в качестве адреса URL указать навигатору только доменное имя сервера, сервер перешлет навигатору свою главную страницу. Имя файла этой страницы зависит от сервера. Большинство серверов на базе операционной системы UNIX посылают по умолчанию файл документа с именем index.html. Сервер Microsoft Information Server может использовать для этой цели имя default.htm или любое другое, определенное при установке сервера, например, home.html или home.htm. Для ссылки на конкретный документ HTML или на файл любого другого объекта необходимо указать в адресе URL его путь, включающий имя файла, например: http://www.glasnet.ru/~frolov/index.html http://www.dials.ccas.ru/frolov/bin/dbsp26.lzh Корневой каталог сервера Web обозначается символом /. В спецификации протокола HTTP сказано, что если путь не задан, то используется корневой каталог. Класс URL в библиотеке классов Java Для работы с ресурсами, заданными своими адресами URL, в библиотеке классов Java имеется очень удобный и мощный класс с названием URL. Простота создания сетевых приложений с использованием этого класса в значительной степени опровергает общераспространенное убеждение в сложности сетевого программирования. Инкапсулируя в себе достаточно сложные процедуры, класс URL предоставляет в распоряжение программиста небольшой набор простых в использовании конструкторов и методов. Конструкторы класса URL Сначала о конструкторах. Их в классе URL имеется четыре штуки. public URL(String spec); Первый из них создает объект URL для сетевого ресурса, адрес URL которого передается конструктору в виде текстовой строки через единственный параметр spec: public URL(String spec); В процессе создания объекта проверяется заданный адрес URL, а также наличие указанного в нем ресурса. Если адрес указан неверно или заданный в нем ресурс отсутствует, возникает исключение MalformedURLException. Это же исключение возникает при попытке использовать протокол, с которым данная система не может работать. Второй вариант конструктора класса URL допускает раздельное указание протокола, адреса узла, номера порта, а также имя файла: public URL(String protocol, String host, int port, String file); Третий вариант предполагает использование номера порта, принятого по умолчанию: public URL(String protocol, String host, String file); Для протокола HTTP это порт с номером 80. И, наконец, четвертый вариант конструктора допускает указание контекста адреса URL и строки адреса URL: public URL(URL context, String spec); Строка контекста позволяет указывать компоненты адреса URL, отсустсвующие в строке spec, такие как протокол, имя узла, файла или номер порта. Методы класса URL Рассмотрим самые интересные методы, определенные в классе URL. Метод openStream Метод openStream позволяет создать входной поток для чтения файла ресурса, связанного с созданным объектом класса URL: public final InputStream openStream(); Для выполнения операции чтения из созданного таким образом потока вы можете использовать метод read, определенный в классе InputStream (любую из его разновидностей). Данную пару методов (openStream из класса URL и read из класса InputStream) можно применить для решения задачи получения содержимого двоичного или текстового файла, хранящегося в одном из каталогов сервера Web. Сделав это, обычное приложение Java или аплет может выполнить локальную обработку полученного файла на компьютере удаленного пользователя. Метод getContent Очень интересен метод getConten. Этот метод определяет и получает содержимое сетевого ресурса, для которого создан объект URL: public final Object getContent(); Практически вы можете использовать метод getContent для получения текстовых файлов, расположенных в сетевых каталогах. К сожалению, данный метод непригоден для получения документов HTML, так как для данного ресурса не определен обработчик соедржимого, предназначенный для создания объекта. Метод getContent не способен создать объект ни из чего другого, кроме текстового файла. Данная проблема, тем не менее, решается очень просто - достаточно вместо метода getContent использовать описанную выше комбинацию методов openStream из класса URL и read из класса InputStream. Метод getHost С помощью метода getHost вы можете определить имя узла, соответствующего данному объекту URL: public String getHost(); Метод getFile Метод getFile позволяет получить информацию о файле, связанном с данным объектом URL: public String getFile(); Метод getPort Метод getPort предназначен для определения номера порта, на котором выполняется связь для объекта URL: public int getPort(); Метод getProtocol С помощью метода getProtocol вы можете определить протокол, с использованием которого установлено соединение с ресурсом, заданным объектом URL: public String getProtocol(); Метод getRef Метод getRef возвращает текстовую строку ссылки на ресурс, соответствующий данному объекту URL: public String getRef(); Метод hashCode Метод hashCode возвращает хэш-код объекта URL: public int hashCode(); Метод sameFile С помощью метода sameFile вы можете определить, ссылаются ли два объекта класса URL на один и тот же ресурс, или нет: public boolean sameFile(URL other); Если объекты ссылаются на один и тот же ресурс, метод sameFile возвращает значение true, если нет - false. Метод equals Вы можете использовать метод equals для определения идентичности адресов URL, заданных двумя объектами класса URL: public boolean equals(Object obj); Если адреса URL идентичны, метод equals возвращает значение true, если нет - значение false. Метод toExternalForm Метод toExternalForm возвращает текстовую строку внешнего представления адреса URL, определенного данным объектом класса URL: public String toExternalForm(); Метод toString Метод toString возвращает текстовую строку, представляющую данный объект класса URL: public String toString(); Метод openConnection Метод openConnection предназначен для создания канала между приложением и сетевым ресурсом, представленным объектом класса URL: public URLConnection openConnection(); Если вы создаете приложение, которое позволяет читать из каталогов сервера Web текстовые или двоичные файлы, можно создать поток методом openStream или получить содержимое текстового ресурса методом getContent. Однако есть и другая возможность. Вначале вы можете создать канал, как объект класса URLConnection, вызвав метод openConnection, а затем создать для этого канала входной поток, воспользовавшись методом getInputStream, определенным в классе URLConnection. Такая методика позволяет определить или установить перед созданием потока некоторые характеристики канала, например, задать кэширование. Однако самая интересная возможность, которую предоставляет этот метод, заключается в организации взаимодействия приложения Java и сервера Web. Подробнее методика организации такого взаимодействия и класс URLConnection будет рассмотрен позже. Приложение URLDemo В качестве практического примера применения класса URL мы создали приложение URLDemo. Это приложение вводит с консоли адрес URL текстового или двоичного файла, расположенного на сервере Web и создает для этого файла входной поток. С использованием данного потока приложение копирует файл на локальный диск компьютера в текущий каталог. Исходный текст приложения Исходный текст приложения URLDemo приведены в листинге 3.2. Листинг 3.2. Файл URLDemo\URLDemo.java // ========================================================= // Копирование файла, расположенного в каталоге // сервера Web, с помощью класса URL // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.net.*; import java.io.*; import java.util.*; // ========================================================= // Класс InetAddressDemo // Главный класс приложения // ========================================================= public class URLDemo { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Введенная строка String sIn; // Строка адреса URL String sURL; // Адрес URL удаленного узла URL u; // Рабочий буфер для копирования файла byte buf[] = new byte[1024]; try { // Вводим адрес URL удаленного узла System.out.println("Enter remote host name..."); System.in.read(bKbdInput); sIn = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(sIn, "\r\n"); sURL = new String((String)st.nextElement()); // Создаем объект класса URL u = new URL(sURL); // Создаем входной поток, связанный с объектом, // адрес URL которого хранится в поле u InputStream is = u.openStream(); // Создаем выходной буферизованный форматированный // поток для записи принятого файла DataOutputStream os = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.dat"))); // Выполняем в цикле чтение файла, расположенного // по адресу u, копируя этот файл в выходной поток while(true) { int nReaded = is.read(buf); if(nReaded == -1) break; os.write(buf, 0, nReaded); } // Закрываем входной и выходной потоки is.close(); os.close(); System.out.println("File received"); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println("Press to terminate..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста Сразу после запуска приложение запрашивает с консоли текстовую строку адреса URL файла, который необходимо переписать через сеть на локальный диск. После удаления символа перевода строки адрес записывается в поле sURL. Далее приложение создает объект класса URL, соответствующий введенному адресу: u = new URL(sURL); На следующем этапе для объекта URL создается входной поток, для чего вызывается метод openStream: InputStream is = u.openStream(); Идентификатор этого потока сохраняется в поле is. Принятый файл будет записан в текущий каталог под именем output.dat. Для этого мы создаем входной буферизованный форматированный поток os, как это показано ниже: DataOutputStream os = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("output.dat"))); После знакомства с главой нашей книги, посвященной работе с потоками, эти строки не должны вызывать у вас никаких вопросов. Операция чтения данных из входного потока и записи в выходной поток выполняется в цикле: while(true) { int nReaded = is.read(buf); if(nReaded == -1) break; os.write(buf, 0, nReaded); } Вначале для входного потока вызывается метод read. Он возвращает количество прочитанных байт данных или значение -1, если был достигнут конец потока. В последнем случае цикл прерывается. Принятые данные размещаются в массиве buf, откуда затем они записываются в выходной поток методом write. Мы записываем в выходной поток столько байт данных, сколько было считано. После того как файл будет принят и записан в выходной поток, мы закрываем оба потока: is.close(); os.close(); Приложение ShowChart Попробуем теперь на практике применить технологию передачи файлов из каталога сервера Web в аплет для локальной обработки. Наше следующее приложение с названием ShowChart получает небольшой текстовый файл с исходными данными для построения круговой диаграммы, содержимое которого представлено ниже: 10,20,5,35,11,10,3,6,80,10,20,5,35,11,10,3,6,80 В этом файле находятся численные значения углов для отдельных секторов диаграммы, причем сумма этих значений равна 360 градусам. Наш аплет принимает этот файл через сеть и рисует круговую диаграмму, показанную на рис. 3.2. Рис. 3.2. Круговая диаграмма, построенная на базе исходных данных, полученных через сеть Файл исходных данных занимает всего 49 байт, поэтому он передается по сети очень быстро. Если бы мы передавали графическое изображение этой диаграммы, статическое или динамическое, подготовленное, например, расширением сервера CGI или ISAPI, объем передаваемых по сети данных был бы намного больше. Исходные тексты приложения Исходные тексты приложения ShowChart приведены в листинге 3.3. Листинг 3.3. Файл ShowChart\ShowChart.java // ========================================================= // Рисование круговой диаграммы, данные для которой // получены от сервера Web через сеть // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; import java.net.*; import java.io.*; import java.util.*; public class ShowChart extends Applet { // Адрес URL файла с данными для круговой диаграммы URL SrcURL; // Содержимое этого файла Object URLContent; // Код ошибки int errno = 0; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ShowChart\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод, получающий управление при инициализации аплета // ------------------------------------------------------- public void init() { try { // Создаем объект класса URL для файла с данными // для круговой диаграммы SrcURL = new URL("http://frolov/chart.txt"); try { // Получаем содержимое этого файла URLContent = SrcURL.openConnection().getContent(); } catch (IOException ioe) { showStatus("getContent exception"); // При возникновении исключения во время получения // содержимого устанавливаем код ошибки, равный 1 errno = 1; } } catch (MalformedURLException uex) { showStatus("MalformedURLException exception"); // При возникновении ошибки в процессе создания // объекта класса URL устанавливаем код ошибки, // равный 2 errno = 2; } } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Строка, в которую будет записано содержимое // файла данных для круговой диаграммы String sChart = ""; // Начальный угол сектора диаграммы Integer AngleFromChart = new Integer(0); // Угол предыдущего сектора диаграммы int PrevAngle = 0; // Случайные компоненты цвета сектора int rColor, gColor, bColor; // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Проверяем, является ли полученное содержимое // текстовой строкой if(URLContent instanceof String) { sChart = (String)URLContent; } // Если нет, устанавливаем код ошибки, равный 3 else errno = 3; // Если произошла ошибка, отображаем ее код // и полученные данные if(errno != 0) showStatus("errno: " + errno + ", sChart: " + sChart); // Если ошибки нет, отображаем полученные данные else showStatus(sChart); // Создаем разборщик текстовой строки для // выделения значений углов в принятом файле данных StringTokenizer st = new StringTokenizer(sChart, ",\r\n"); // Цикл по всем значениям while(st.hasMoreElements()) { // Выбираем случайный цвет для рисования rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); // Устанавливаем выбранный цвет в контексте // отображения g.setColor(new Color(rColor, gColor, bColor)); // Получаем значение угла String angle = (String)st.nextElement(); // Преобразуем его в численное значение AngleFromChart = new Integer(angle) ; // Рисуем сектор диаграммы g.fillArc(0, 0, 200, 200, PrevAngle, AngleFromChart.intValue()); // Увеличиваем текущее значение угла PrevAngle += AngleFromChart.intValue(); } } } Исходный текст документа HTML, созданного автоматически для нашего аплета, представлен в листинге 3.4. Листинг 3.4. Файл ShowChart\ShowChart.html ShowChart

The source. Описание исходного текста Приложение ShowChart получает содержимое файла исходных данных для построения круговой диаграммы с помощью класса URL. Как вы увидите, для получения содержимого этого файла оно не создает поток ввода явным образом, как это делало предыдущее приложение (хотя могло бы). Вместо этого оно пользуется методом getContent, определенным в классе URL. Поля класса ShowChart В классе ShowChart определены три поля. Поле SrcURL класса URL хранит адрес URL файла исходных данных для круговой диаграммы. В поле URLContent типа Object будет переписано содержимое этого файла. И, наконец, в поле errno хранится текущий код ошибки, если она возникла, или нулевое значение, если все операции были выполнены без ошибок. Метод init Во время инициализации метод init создает объект класса URL для файла исходных данных: SrcURL = new URL("http://frolov/chart.txt"); Здесь для экономии места в книге мы указали адрес URL файла исходных данных непосредственно в программе, однако вы можете передать этот адрес аплету через параметр в документе HTML. Далее для нашего объекта URL мы создаем канал и получаем содержимое объекта (то есть исходные данные для построения диаграммы): URLContent = SrcURL.openConnection().getContent(); Здесь использована двухступенчатая процедура получения содержимого с созданием канала как объекта класса URLConnection. Вы также можете упростить этот код, воспользовавшись методом getContent из класса URL: URLContent = SrcURL.getContent(); Результат в обоих случаях будет одинаковый - содержимое файла исходных данных окажется записанным в поле URLContent класса Object. Если при создании объекта класса URL возникло исключение, метод init записывает в поле errno код ошибки, равный 2, записывая при этом в строку состояния навигатора сообщение “MalformedURLException exception”. В том случае, когда объект класса URL создан успешно, а исключение возникло в процессе получения содержимого файла, в поле errno записывается значение 1, а в строку состояния навигатора - сообщение "getContent exception". Метод paint После раскрашивания фона окна аплета и рисования вокруг него рамки метод paint приступает к рисованию круговой диаграммы. Прежде всего метод проверяет, является ли полученный из сети объект текстовой строкой класса String. Если является, то выполняется явное преобразование типа: if(URLContent instanceof String) { sChart = (String)URLContent; } В случае успеха в переменной sChart будет находиться строка исходных данных для построения диаграммы, а при ошибке - строка “”, записанная туда при инициализации. Кроме того, в поле errno записывается значение 3. Далее метод paint проверяет, были ли ошибки при создании объекта URL, получении содержимого файла исходных данных или преобразования данных в строку класса String. Если были, то в строку состояния навигатора записывается код ошибки и содержимое строки sChart. Если же ошибок не было, то в строке состояния отображаются исходные данные: if(errno != 0) showStatus("errno: " + errno + ", sChart: " + sChart); else showStatus(sChart); На следующем этапе обработчик paint приступает к построению диаграммы. Первым делом создается разборщик строки исходных данных: StringTokenizer st = new StringTokenizer(sChart, ",\r\n"); В качестве разделителей для этого разборщика указывается запятая, символ возврата каретки и перевода строки. Рисование секторов диаграммы выполняется в цикле, условием выхода из которого является завершение разбора строки исходных данных: while(st.hasMoreElements()) { . . . } Для того чтобы секторы диаграммы не сливались, они должны иметь разный цвет. Цвет сектора можно было бы передавать вместе со значением угла через файл исходных данных, однако мы применили более простой способ раскаршивания секторов - в случайные цвета. Мы получаем случайные компоненты цвета сектора, а затем выбираем цвет в контекст отображения: rColor = (int)(255 * Math.random()); gColor = (int)(255 * Math.random()); bColor = (int)(255 * Math.random()); g.setColor(new Color(rColor, gColor, bColor)); С помощью метода nextElement мы получаем очередное значение угла сектора и сохраняем его в переменной angle: String angle = (String)st.nextElement(); Далее с помощью конструктора класса Integer это значение преобразуется в численное: AngleFromChart = new Integer(angle) ; Рисование сектора круговой диаграммы выполняется с помощью метода fillArc, который был рассмотрен в предыдущем томе “Библиотеки системного программиста”, посвященном языку программирования Java: g.fillArc(0, 0, 200, 200, PrevAngle, AngleFromChart.intValue()); В качестве начального значения угла сектора используется значение из переменной PrevAngle. Сразу после инициализации в эту переменную записывается нулевое значение. Конечный угол сектора задается как AngleFromChart.intValue(), то есть указывается значение, полученное из принятого по сети файла исходных данных. После завершения рисования очередного сектора круговой диаграммы начальное значение PrevAngle увеличивается на величину угла нарисованного сектора: PrevAngle += AngleFromChart.intValue(); Передача данных с использованием сокетов В библиотеке классов Java есть очень удобное средство, с помощью которых можно организовать взаимодействие между приложениями Java и аплетами, работающими как на одном и том же, так и на разных узлах сети TCP/IP. Это средство, родившееся в мире операционной системы UNIX, - так называемые сокеты (sockets). Что такое сокеты? Вы можете представить себе сокеты в виде двух розеток, в которые включен кабель, предназначенный для передачи данных через сеть. Переходя к компьютерной терминологии, скажем, что сокеты - это программный интерфейс, предназначенный для передачи данных между приложениями. Прежде чем приложение сможет выполнять передачу аили прием данных, оно должно создать сокет, указав при этом адрес узла IP, номер порта, через который будут передаваться данные, и тип сокета. С адресом узла IP вы уже сталкивались. Номер порта служит для идентификации приложения. Заметим, что существуют так называемые “хорошо известные” (well known) номера портов, зарезервированные для различных приложений. Например, порт с номером 80 зарезервирован для использования серверами Web при обмене данными через протокол HTTP. Что же касается типов сокетов, то их два - потоковые и датаграммные. С помощью потоковых сокетов вы можете создавать каналы передачи данных между двумя приложениями Java в виде потоков, которые мы уже рассматривали во второй главе. Потоки могут быть входными или выходными, обычными или форматированными, с использованием или без использования буферизации. Скоро вы убедитесь, что организовать обмен данными между приложениями Java с использованием потоковых сокетов не труднее, чем работать через потоки с обычными файлами. Заметим, что потоковые сокеты позволяют передавать данные только между двумя приложениями, так как они предполагают создание канала между этими приложениями. Однако иногда нужно обеспечить взаимодействие нескольких клиентских приложений с одним серверным. В этом случае вы можете либо создавать в серверном приложении отдельные задачи и отдельные каналы для каждого клиентского приложения, либо воспользоваться датаграммными сокетами. Последние позволяют передавать данные сразу всем узлам сети, хотя такая возможность редко используется и часто блокируется администраторами сети. Для передачи данных через датаграммные сокеты вам не нужно создавать канал - данные посылаются непосредственно тому приложению, для которого они предназначены с использованием адреса этого приложения в виде сокета и номера порта. При этом одно клиентское приложение может обмениваться данными с несколькими серверными приложениями или наоборот, одно серверное приложение - с несколькими клиентскими. К сожалению, датаграммные сокеты не гарантируют доставку передаваемых пакетов данных. Даже если пакеты данных, передаваемые через такие сокеты, дошли до адресата, не гарантируется, что они будут получены в той же самой последовательности, в которой были переданы. Потоковые сокеты, напротив, гарантируют доставку пакетов данных, причем в правильной последовательности. Причина отстутствия гарантии доставки данных при использовании датаграммных сокетов заключается в использовании такими сокетами протокола UDP, который, в свою очередь, основан на протоколе с негарантированной доставкой IP. Потоковые сокеты работают через протокол гарантированной доставки TCP. В 23 томе “Библиотеки системного программиста”, который называется “Глобальные сети компьютеров. Практическое введение в Internet, E-Mail, FTP, WWW и HTML, программирование для Windows Sockets” мы уже рассказывали про сокеты в среде операционной системы Microsoft Windows. В этой книге вы найдете примеры приложений, составленных на языке программирования С и работающих как с потоковыми, так и с датаграммными сокетами. Работа с потоковыми сокетами Как мы уже говорили, интерфейс сокетов позволяет передавать данные между двумя приложениями, работающими на одном или разных узлах сети. В процессе создания канала передачи данных одно из этих приложений выполняет роль сервера, а другое - роль клиента. После того как канал будет создан, приложения становятся равноправными - они могут передавать друг другу данные симметричным образом. Рассмотрим этот процесс в деталях. Инициализация сервера Вначале мы рассмотрим действия приложения, которое на момент инициализации является сервером. Первое, что должно сделать серверное приложение, это создать объект класса ServerSocket, указав конструктору этого класса номер используемого порта: ServerSocket ss; ss = new ServerSocket(9999); Заметим, что объект класса ServerSocket вовсе не является сокетом. Он предназначен всего лишь для установки канала связи с клиентским приложением, после чего создается сокет класса Socket, пригодный для передачи данных. Установка канала связи с клиентским приложением выполняется при помощи метода accept, определенного в классе ServerSocket: Socket s; s = ss.accept(); Метод accept приостанавливает работу вызвавшей его задачи до тех пор, пока клиентское приложение не установит канал связи с сервером. Если ваше приложение однозадачное, его работа будет блокирована до момента установки канала связи. Избежать полной блокировки приложения можно, если выполнять создание канала передачи данных в отдельной задаче. Как только канал будет создан, вы можете использовать сокет сервера для образования входного и выходного потока класса InputStream и OutputStream, соответственно: InputStream is; OutputStream os; is = s.getInputStream(); os = s.getOutputStream(); Эти потоки можно использовать таким же образом, что и потоки, связанные с файлами. Обратите также внимание на то, что при создании серверного сокета мы не указали адрес IP и тип сокета, ограничившись только номером порта. Что касается адреса IP, то он, очевидно, равен адресу IP узла, на котором запущено приложение сервера. В классе ServerSocket определен метод getInetAddress, позволяющий определить этот адрес: public InetAddress getInetAddress(); Тип сокета указывать не нужно, так как для работы с датаграммными сокетами предназначен класс DatagramSocket, который мы рассмотрим позже. Инициализация клиента Процесс инициализации клиентского приложения выглядит весьма просто. Клиент должен просто создать сокет как объект класса Socket, указав адрес IP серверного приложения и номер порта, используемого сервером: Socket s; s = new Socket("localhost",9999); Здесь в качестве адреса IP мы указали специальный адрес localhost, предназначенный для тестирования сетевых приложений, а в качестве номера порта - ззначение 9999, использованное сервером. Теперь можно создавать входной и выходной потоки. На стороне клиента эта операция выполняется точно также, как и на стороне сервера: InputStream is; OutputStream os; is = s.getInputStream(); os = s.getOutputStream(); Передача данных между клиентом и сервером После того как серверное и клиентское приложения создали потоки для приема и передачи данных, оба этих приложения могут читать и писать в канал данных, вызывая методы read и write, определенные в классах InputStream и OutputStream. Ниже мы представили фрагмент кода, в котором приложение вначале читает данные из входного потока в буфер buf, а затем записывает прочитанные данные в выходной поток: byte buf[] = new byte[512]; int lenght; lenght = is.read(buf); os.write(buf, 0, lenght); os.flush(); На базе потоков класса InputStream и OutputStream вы можете создать буферизованные потоки и потоки для передачи форматированных данных, о которых мы рассказывали раньше. Завершение работы сервера и клиента После завершения передачи данных вы должны закрыть потоки, вызвав метод close: is.close(); os.close(); Когда канал передачи данных больше не нужен, сервер и клиент должны закрыть сокет, вызвав метод close, определенный в классе Socket: s.close(); Серверное приложение, кроме того, должно закрыть соединение, вызвав метод close для объекта класса ServerSocket: ss.close(); Конструкторы и методы класса Socket После краткого введения в сокеты приведем описание наиболее интересных конструкторов и методов класса Socket. Конструкторы класса Socket Чаще всего для создания сокетов в клиентских приложениях вы будете использовать один из двух конструкторов, прототипы которых приведены ниже: public Socket(String host, int port); public Socket(InetAddress address, int port); Первый из этих конструкторов позволяет указывать адрес серверного узла в виде текстовой строки, второй - в виде ссылки на объект класса InetAddress. Вторым параметром задается номер порта, с использованием которого будут передаваться данные. В классе Socket определена еще одна пара конструкторов, которая, однако не рекомендуется для использования: public Socket(String host, int port, boolean stream); public Socket(InetAddress address, int port, boolean stream); В этих конструкторах последний параметр определяет тип сокета. Если этот параметр равен true, создается потоковый сокет, а если false - датаграммный. Заметим, однако, что для работы с датаграммными сокетами следует использовать класс DatagramSocket. Методы класса Socket Перечислим наиболее интересные, на наш взгляд, методы класса Socket. Прежде всего, это методы getInputStream и getOutputStream, предназначенные для создания входного и выходного потока, соответственно: public InputStream getInputStream(); public OutputStream getOutputStream(); Эти потоки связаны с сокетом и должны быть использованы для передачи данных по каналу связи. Методы getInetAddress и getPort позволяют определить адрес IP и номер порта, связанные с данным сокетом (для удаленного узла): public InetAddress getInetAddress(); public int getPort(); Метод getLocalPort возвращает для данного сокета номер локального порта: public int getLocalPort(); После того как работа с сокетом завершена, его необходимо закрыть методом close: public void close(); И, наконец, метод toString возвращает текстовую строку, представляющую сокет: public String toString(); Приложения SocketServ и SocketClient В качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом. Приложение SocketServ выводит на консоль строку “Socket Server Application” и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient. Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли. Когда пользователь вводит строку “quit”, цикл ввода и передачи строк завершается. Весь процесс показан на рис. 3.3. Рис. 3.3. Передача данных между приложениями SocketClient и SocketServ через потоковый сокет Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой “quit”, завершившая работу приложений. Исходный текст серверного приложения SocketServ Исходный текст серверного приложения SocketServ приведен в листинге 3.5. Листинг 3.5. Файл SocketServ\SocketServ.java // ========================================================= // Использование потоковых сокетов. // Приложение сервера // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class SocketServ { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Объект класса ServerSocket для создания канала ServerSocket ss; // Сокет сервера Socket s; // Входной поток для приема команд от клиента InputStream is; // Выходной поток для передачи ответа клиенту OutputStream os; try { System.out.println("Socket Server Application"); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Создаем объект класса ServerSocket ss = new ServerSocket(9999); // Ожидаем соединение s = ss.accept(); // Открываем входной поток для приема // команд от клиента is = s.getInputStream(); // Открываем выходной поток для передачи // ответа клиенту os = s.getOutputStream(); // Буфер для чтения команд byte buf[] = new byte[512]; // Размер принятого блока данных int lenght; // Цикл обработки команд, полученных от клиента while(true) { // Получаем команду lenght = is.read(buf); // Если входной поток исчерпан, завершаем // цикл обработки команд if(lenght == -1) break; // Отображаем принятую команду на консоли сервера // Формируем строку из принятого блока String str = new String(buf, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\r\n"); str = new String((String)st.nextElement()); // Выводим строку команды на консоль System.out.println("> " + str); // Посылаем принятую команду обратно клиенту os.write(buf, 0, lenght); // Сбрасываем буфер выходного потока os.flush(); } // Закрываем входной и выходной потоки is.close(); os.close(); // Закрываем сокет сервера s.close(); // Закрываем соединение ss.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста серверного приложения SocketServ В методе main, получающем управление сразу после запуска приложения, мы определили несколько переменных. Массив bKbdInput размером 256 байт предназначен для хранения строк, введенных при помощи клавиатуры. В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет): ServerSocket ss; Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket: Socket s; Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream: InputStream is; OutputStream os; В эти переменные будут записаны ссылки на входной и выходной поток данных, которые связаны с сокетом. После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999: ss = new ServerSocket(9999); Конструктор возвращает ссылку на объект, с использованием которого можно установить канал передачи данных с клиентом. Канал устанавливается методом accept: s = ss.accept(); Этот метод переводит приложение в состояние ожидания до тех пор, пока не будет установлен канал передачи данных. Метод accept в случае успешного создания канала передачи данных возвращает ссылку на сокет, с применением которого нужно принимать и передавать данные. На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно: is = s.getInputStream(); os = s.getOutputStream(); Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных: byte buf[] = new byte[512]; int lenght; Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения. Для чтения строки мы вызываем метод read применительно ко входному потоку: lenght = is.read(buf); Этот метод возвращает управление только после того, как все данные будут прочитаны, блокируя приложение на время своей работы. Если такая блокировка нежелательна, вам следует выполнять обмен данными через сокет в отдельной задаче. Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных: if(lenght == -1) break; После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера: System.out.println("> " + str); Затем полученная строка отправляется обратно клиентскому приложению, для чего вызывается метод write: os.write(buf, 0, lenght); Методу write передается ссылка на массив, смещение начала данных в этом массиве, равное нулю, и размер принятого блока данных. Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush: os.flush(); И хотя в нашем случае мы не пользуемся буферизованными потоками, мы включили вызов этого метода для примера. Теперь о завершающих действиях после прерывания цикла получения, отображения и передачи строк. Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных: is.close(); os.close(); s.close(); ss.close(); Исходный текст клиентского приложения SocketClient Исходный текст клиентского приложения SocketClient приведен в листинге 3.6. Листинг 3.6. Файл SocketClient\SocketClient.java // ========================================================= // Использование потоковых сокетов. // Приложение клиента // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class SocketClient { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Сокет для связи с сервером Socket s; // Входной поток для приема данных от сервера InputStream is; // Выходной поток для передачи данных серверу OutputStream os; try { // Выводим строку приглашения System.out.println("Socket Client Application" + "\nEnter any string or 'quit' to exit..."); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Открываем сокет s = new Socket("localhost",9999); // Создаем входной поток для приема данных от сервера is = s.getInputStream(); // Создаем выходной поток для передачи данных серверу os = s.getOutputStream(); // Буфер для передачи данных byte buf[] = new byte[512]; // Размер принятого блока данных int length; // Рабочая строка String str; // Вводим команды и передаем их серверу while(true) { // Читаем строку команды с клавиатуры length = System.in.read(bKbdInput); // Если строка не пустая, обрабатываем ее if(length != 1) { // Преобразуем строку в формат String str = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); // Выводим передаваемую строку команды // на консоль для контроля System.out.println("> " + str); // Записываем строку в выходной поток, // передавая ее таким образом серверу os.write(bKbdInput, 0, length); // Сбрасываем буфер выходного потока os.flush(); // Принимаем ответ сервера length = is.read(buf); if(length == -1) break; // Отображаем принятую строку на консоли str = new String(buf, 0); st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); System.out.println(">> " + str); // Если введена строка 'quit', завершаем // работу приложения if(str.equals("quit")) break; } } // Закрываем входной и выходной потоки is.close(); os.close(); // Закрываем сокет s.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста клиентского приложения SocketClient Внутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s. После вывода на консоль приглашающей строки клиентское приложение создает сокет, вызывая конструктор класса Socket: s = new Socket("localhost",9999); В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка “localhost”. Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение. После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом: is = s.getInputStream(); os = s.getOutputStream(); Теперь клиентское приложение готово обмениваться данными с сервером. Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки “quit”. Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput: length = System.in.read(bKbdInput); Количество введенных символов сохраняется в переменной length. Далее если пользователь ввел строку, а не просто нажал на клавишу , эта строка отображается на консоли и передается серверу: os.write(bKbdInput, 0, length); os.flush(); Сразу после передачи сбрасывается буфер выходного потока. Далее приложение читает ответ, посылаемый сервером, в буфер buf: length = is.read(buf); Напомним, что наш сервер посылает клиенту принятую строку в неизменном виде. Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк: if(length == -1) break; Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента: System.out.println(">> " + str); Перед завершением своей работы клиент закрывает входной и выходной потоки, а также сокет, на котором выполнялась передача данных: is.close(); os.close(); s.close(); Использование датаграммных сокетов Как мы уже говорили, датаграммные сокеты не гарантируют доставку пакетов данных. Тем не менее, они работают быстрее потоковых и обеспечивают возможность широковещательной расслыки пакетов данных одновременно всем узлам сети. Последняя возможность используется не очень широко в сети Internet, однако в корпоративной сети Intranet вы вполне можете ей воспользоваться. Для работы с датаграммными сокетами приложение должно создать сокет на базе класса DatagramSocket, а также подготовить объект класса DatagramPacket, в который будет записан принятый от партнера по сети блок данных. Канал, а также входные и выходные потоки создавать не нужно. Данные передаются и принимаются методами send и receive, определенными в классе DatagramSocket. Класс DatagramSocket Рассмотрим конструкторы и методы класса DatagramSocket, предназначенного для создания и использования датаграммных сокетов. В классе DatagramSocket определены два конструктора, прототипы которых представлены ниже: public DatagramSocket(int port); public DatagramSocket(); Первый из этих конструкторов позволяет определить порт для сокета, второй предполагает использование любого свободного порта. Обычно серверные приложения работают с использованием какого-то заранее определенного порта, номер которого известен клиентским приложениям. Поэтому для серверных приложений больше подходит первый из приведенных выше конструкторов. Клиентские приложения, напротив, часто применяют любые свободные на локальном узле порты, поэтому для них годится конструктор без параметров. Кстати, с помощью метода getLocalPort приложение всегда может узнать номер порта, закрепленного за данным сокетом: public int getLocalPort(); Прием и передача данных на датаграммном сокете выполняется с помощью методов receive и send, соответственно: public void receive(DatagramPacket p); public void send(DatagramPacket p); В качестве параметра этим методам передается ссылка на пакет данных (соответственно, принимаемый и передаваемый), определенный как объект класса DatagramPacket. Этот класс будет рассмотрен в следующем разделе нашей книги. Еще один метод в классе DatagramSocket, которым вы будете пользоваться, это метод close, предназначенный для закрытия сокета: public void close(); Напомним, что сборка мусора в Java выполняется только для объектов, находящихся в оперативной памяти. Такие объекты, как потоки и сокеты, вы должны закрывать после использования самостоятельно. Класс DatagramPacket Перед тем как принимать или передавать данные с использованием методов receive и send вы должны подготовить объекты класса DatagramPacket. Метод receive запишет в такой объект принятые данные, а метод send - перешлет данные из объекта класса DatagramPacket узлу, адрес которого указан в пакете. Подготовка объекта класса DatagramPacket для приема пакетов выполняется с помощью следующего конструктора: public DatagramPacket(byte ibuf[], int ilength); Этому конструктору передается ссылка на массив ibuf, в который нужно будет записать данные, и размер этого массива ilength. Если вам нужно подготовить пакет для передачи, воспользуйтесь конструктором, который дополнительно позволяет задать адрес IP iaddr и номер порта iport адресата: public DatagramPacket(byte ibuf[], int ilength, InetAddress iaddr, int iport); Таким образом, информация о том, в какой узел и на какой порт необходимо доставить пакет данных, хранится не в сокете, а в пакете, то есть в объекте класса DatagramPacket. Помимо только что описанных конструкторов, в классе DatagramPacket определены четыре метода, позволяющие получить данные и информацию об адресе узла, из которого пришел пакет, или для которого предназначен пакет. Метод getData возвращает ссылку на массив данных пакета: public byte[] getData(); Размер пакета, данные из которого хранятся в этом массиве, легко определить с помощью метода getLength: public int getLength(); Методы getAddress и getPort позволяют определить адрес и номер порта узла, откуда пришел пакет, или узла, для которого предназначен пакет: public InetAddress getAddress(); public int getPort(); Если вы создаете клиент-серверную систему, в которой сервер имеет заранее известный адрес и номер порта, а клиенты - произвольные адреса и различные номера портов, то после получения пакета от клиента сервер может определить с помощью методов getAddress и getPort адрес клиента для установления с ним связи. Если же адрес сервера неизвестен, клиент может посылать широковещательные пакеты, указав в объекте класса DatagramPacket адрес сети. Такая методика обычно используется в локальных сетях. Как указать адрес сети? Напомним, что адрес IP состоит из двух частей - адреса сети и адреса узла. Для разделения компонент 32-разрядного адреса IP используется 32-разрядная маска, в которой битам адреса сети соответствуют единицы, а битам адреса узла - нули. Например, адрес узла может быть указан как 193.24.111.2. Исходя из значения старшего байта адреса, это сеть класса С, для которой по умолчанию используется маска 255.255.255.0. Следовательно, адрес сети будет такой: 193.24.111.0. Подробнее об адресации в сетях TCP/IP вы можете прочитать в 23 томе “Библиотеки системного программиста”, о котором мы упоминали в разделе “Передача данных с использованием сокетов” нашей книги. Приложения DatagramServer и DatagramClient Приложения DatagramServer и DatagramClient иллюстрируют применение датаграммных сокетов для передачи данных от нескольких копий одного и того же клиента одному серверу с известным адресом и номером порта. Клиентские приложения посылают серверу строки, которые пользователь вводит с клавиатуры. Сервер принимает эти строки, отображая их в своем консольном окне вместе с номером порта клиента (рис. 3.4). Рис. 3.4. Передача данных между приложениями DatagramClient и DatagramServer через датаграммный сокет Когда с консоли клиента будет введена строка “quit”, этот клиент и сервер завершает свою работу. Работа остальных клиентов также может быть завершена подобным образом, причем независимо от того, работает сервер, или нет. Наши клиенты не получают от сервера никакого подтверждения в ответ на переданные ему пакеты. Вы можете изменить программу клиента, добавив такую возможность. Однако учтите, что так как датаграммные сокеты не гарантируют доставки пакетов, ожидание ответа может продлиться бесконечно долго. Чтобы избежать этого, ваше приложение должно выполнять работу с сервером в отдельной задаче, причем главная задача должна ждать ответ ограниченное время. Все что вам нужно, чтобы реализовать работу подобным образом, вы найдете в первой главе нашей книги, посвященной мультизадачности в приложениях Java. Исходный текст приложения DatagramServer Исходный текст приложения DatagramServer вы найдете в листинге 3.7. Листинг 3.7. Файл DatagramServer\DatagramServer.java // ========================================================= // Использование датаграммных сокетов // Приложение сервера // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class DatagramServer { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Буфер для чтения команд byte buf[] = new byte[512]; // Сокет сервера DatagramSocket s; // Принимаемый пакет DatagramPacket pinp; // Адрес узла, откуда пришел принятый пакет InetAddress SrcAddress; // Порт, откуда пришел принятый пакет int SrcPort; try { // Выводим строку приглашения System.out.println( "Datagramm Socket Server Application"); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Создаем сокет сервера s = new DatagramSocket(9998); // Создаем пакет для приема команд pinp = new DatagramPacket(buf, 512); // Цикл обработки команд, полученных от клиента while(true) { // Принимаем пакет от клиента s.receive(pinp); // Получаем адрес узла, приславшего пакет SrcAddress = pinp.getAddress(); // Получаем порт, на котором был передан пакет SrcPort = pinp.getPort(); // Отображаем принятую команду на консоли сервера // Формируем строку из принятого блока String str = new String(buf, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\r\n"); str = new String((String)st.nextElement()); // Выводим строку команды на консоль System.out.println("> " + str + " < " + "port: " + SrcPort); // Если пришла команда 'quit', прерываем цикл if(str.equals("quit")) break; } // Закрываем сокет сервера s.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста приложения DatagramServer Внутри функции main мы определили несколько переменных. Массив bKbdInput хранит строку, введенную с клавиатуры. Массив buf используется для хранения строк (команд), которые сервер получает от клиентских приложений. В переменной s класса DatagramSocket хранится ссылка на датаграмный сокет, который будет использован для приема команд от клиентских приложений. Переменная pinp класса DatagramPacket хранит пакеты, полученные сервером из сети. Переменные SrcAddress (класса InetAddress) и SrcPort типа int хранят, соответственно, адрес и порт узла, отправившего пакет. Первое, что делает сервер - это создание датаграммного сокета: s = new DatagramSocket(9998); Конструктору передается номер порта 9998, на котором сервер будет принимать пакеты данных от клиентских приложений. После создания сокета сервер создает объекта класса DatagramPacket, в который будут записываться приходящие от клиентов пакеты: pinp = new DatagramPacket(buf, 512); Конструктор пакета получает ссылку на массив buf, в который нужно будет записывать приходящие по сети данные, и размер этого массива, равный 512 байт. Далее сервер запускает цикл приема пакетов и отображения их содержимого. Пакет принимается простым вызовом метода receive из класса DatagramSocket: s.receive(pinp); Этот метод блокирует работу вызвавшей его задачи до тех пор, пока по данному сокету не будет получен пакет данных. Когда пакет будет получен, наше приложение определяет адрес и порт отправителя: SrcAddress = pinp.getAddress(); SrcPort = pinp.getPort(); Эта информация может пригодиться, если вы будете посылать отправителю ответный пакет. Наше приложение ответные пакеты не посылает. Оно преобразует принятые данные в текстовую староку класса String, добавляет к этой строке номер порта отправителя и отображает эту информацию на консоли: System.out.println("> " + str + " < " + "port: " + SrcPort); Цикл приема команд завершается, если от клиента пришла строка “quit”: if(str.equals("quit")) break; Перед тем как завершить свою работу, наше приложение закрывает датаграммный сокет, вызывая для этого метод close: s.close(); Исходный текст приложения DatagramClient В листинге 3.8 приведен исходный текст приложения DatagramClient. Листинг 3.8. Файл DatagramClient\DatagramClient.java // ========================================================= // Использование датаграммных сокетов // Приложение клиента // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class DatagramClient { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Размер введенной строки int length; // Рабочая строка String str; // Сокет клиента DatagramSocket s; // Передаваемый пакет DatagramPacket pout; try { // Выводим строку приглашения System.out.println( "Datagram Socket Client Application" + "\nEnter any string or 'quit' to exit..."); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Получаем адрес локального узла InetAddress OutAddress = InetAddress.getLocalHost(); // Создаем сокет с использованием любого // свободного порта s = new DatagramSocket(); // Создаем передаваемый пакет pout = new DatagramPacket(bKbdInput, bKbdInput.length, OutAddress, 9998); // Цикл передачи команд серверу while(true) { // Читаем строку команды с клавиатуры length = System.in.read(bKbdInput); // Если строка не пустая, обрабатываем ее if(length != 1) { // Преобразуем строку в формат String str = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); // Выводим передаваемую строку команды // на консоль для контроля System.out.println("> " + str); // Посылаем пакет серверу s.send(pout); // Если введена команда 'quit', прерываем цикл if(str.equals("quit")) break; } } // Закрываем сокет s.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста приложения DatagramClient Внутри метода main определен массив bKbdInput, предназначенный для хранения данных, введенных с клавиатуры, переменная length, в которой хранится размер этих данных, рабочая строка str класса String, датаграммный сокет s и пакет pout класса DatagramPacket. Прежде всего приложение определяет адрес узла, на котором оно выполняется, вызывая метод getLocalHost: InetAddress OutAddress = InetAddress.getLocalHost(); Этот адрес будет использован для формирования передаваемого пакета данных. Затем клиент создает датаграммный сокет, применяя для этого конструктор без параметров: s = new DatagramSocket(); Напомним, что в этом случае для сокета выделяется любой свободный порт. На следующем шаге приложение формирует передаваемый пакет, вызывая конструктор класса DatagramPacket: pout = new DatagramPacket(bKbdInput, bKbdInput.length, OutAddress, 9998); Этому конструктору указывается адрес массива, содержащего введенные с клавиатуры данные, размер этого массива, адрес локального узла, на который нужно передать пакет, и номер порта серверного приложения. Теперь все готово для запуска цикла передачи команд от клиента к серверу. В этом цикле выполняется чтение строки с клавиатуры, причем размер прочитанной строки сохраняется в переменной length: length = System.in.read(bKbdInput); Далее, если строка не состоит из одного лишь символа перехода на новую строку, она отображается на косоли и посылается серверу методом send: s.send(pout); После того как пользователь введет строку “quit”, цикл завершается. Вслед за этим приложение закрывает датаграммный сокет: s.close(); Связь приложений Java с расширениями сервера Web Итак, мы расказали вам, как приложения Java могут получать с сервера Web для обработки произвольные файлы, а также как они могут передавать данные друг другу с применением потоковых или датаграммных сокетов. Однако наиболее впечатляющие возможности открываются, если организовать взаимодействие между приложением Java и расширением сервера Web, таким как CGI или ISAPI. В этом случае приложения или аплеты Java могли бы посылать произвольные данные расширению сервера Web для обработки, а затем получать результат этой обработки в виде файла. О том, как сделать расширение сервера Web с применением интерфейса CGI или ISAPI вы можете узнать из 29 тома “Библиотеки системного программиста”, который называется “Сервер Web своими руками”. Если вы никогда раньше не создавали расширений сервера Web, мы настоятельно рекомендуем вам ознакомиться с этой книгой перед тем как продолжить работу над данным разделом. Взаимодействие приложения Java и расширения сервера Web Методика организации взаимодействия приложений Java и расширений сервера Web основана на применении классов URL и URLConnection. Приложение Java, желающее работать с расширением сервера Web, создает объект класса URL для программы расширения (то есть для исполняемого модуля расширения CGI или библиотеки динамической компоновки DLL расширения ISAPI). Далее приложение получает ссылку на канал передачи данных с этим расширением как объекта класса URLConnection. Затем, пользуясь методами getOutputStream и getInputStream из класса URLConnection, приложение создает с расширением сервера Web выходной и входной канал передачи данных. Когда данные передаются приложением в выходной канал, созданный подобным образом, он попадает в стандартный поток ввода приложения CGI, как будто бы данные пришли методом POST из формы, определенной в документе HTML. Обработав полученные данные, расширение CGI записывает их в свой стандартный выходной поток, после чего эти данные становятся доступны приложению Java через входной поток, открытый методом getInputStream класса URLConnection. На рис. 3.5 показаны потоки данных для описанной выше схемы взаимодействия приложения Java и расширения сервреа Web с интерфейсом CGI. Рис. 3.5. Взаимодействие приложения Java с расширением сервера Web на базе интерфейса CGI Расширения ISAPI работают аналогично, однако они получают данные не из стандратного входного потока, а с помощью вызова специально предназначенной для этого функции интерфейса ISAPI. Вместо стандартного потока вывода также применяется специальная функция. Подробности вы можете узнать из 29 тома “Библиотеки системного программиста”. Класс URLConnection Напомним, что в классе URL, рассмотренном нами в начале этой главы, мы привели прототип метода openConnection, возвращающий для заданного объекта класса URL ссылку на объект URLConnection: public URLConnection openConnection(); Что мы можем получить, имея ссылку на этот объект? Прежде всего, пользуясь этой ссылкой, мы можем получить содержимое объекта, адресуемое соответствующим объектом URL, методом getContent: public Object getContent(); Заметим, что метод с таким же названием есть и в классе URL. Поэтому если все, что вы хотите сделать, это получение содержимое файла, адресуемое объектом класса URL, то нет никакой необходимости обращаться к классу URLConnection. Метод getInputStream позволяет открыть входной поток данных, с помощью которого можно считать файл или получить данные от расширения сервера Web: public InputStream getInputStream(); В классе URLConnection определен также метод getOutputStream, позволяющий открыть выходной поток данных: public OutputStream getOutputStream(); Не следует думать, что этот поток можно использовать для записи файлов в каталоги сервера Web. Однако для этого потока есть лучшее применение - с его помощью можно передать данные расширению сервера Web. Рассмотрим еще несколько полезных методов, определенных в классе URLConnection. Метод connect предназначен для установки соединения с объектом, на который ссылается объект класса URL: public abstract void connect(); Перед установкой соединения приложение может установить различные параметры соединения. Некоторые из методов, предназначенных для этого, приведены ниже: // Включение или отключение кэширования по умолчанию public void setDefaultUseCaches(boolean defaultusecaches); // Включение или отключение кэширования public void setUseCaches(boolean usecaches); // Возможность использования потока для ввода public void setDoInput(boolean doinput); // Возможность использования потока для вывода public void setDoOutput(boolean dooutput); // Установка даты модификации документа public void setIfModifiedSince(long ifmodifiedsince); В классе URLConnection есть методы, позволяющие определить значения параметров, установленных только что описанными методами: public boolean getDefaultUseCaches(); public boolean getUseCaches(); public boolean getDoInput(); public boolean getDoOutput(); public long getIfModifiedSince(); Определенный интерес могут представлять методы, предназначенные для извлечения информации из заголовка протокола HTTP: // Метод возвращает содержимое заголовка content-encoding // (кодировка ресурса, на который ссылается URL) public String getContentEncoding(); // Метод возвращает содержимое заголовка content-length // (размер документа) public int getContentLength(); // Метод возвращает содержимое заголовка content-type // (тип содержимого) public String getContentType(); // Метод возвращает содержимое заголовка date // (дата посылки ресурса в секундах с 1 января 1970 года) public long getDate(); // Метод возвращает содержимое заголовка last-modified // (дата изменения ресурса в секундах с 1 января 1970 года) public long getLastModified(); // Метод возвращает содержимое заголовка expires // (дата устаревания ресурса в секундах с // 1 января 1970 года) public long getExpiration(); Другие методы, определенные в классе URLConnection, позволяют получить все заголовки или заголовки с заданным номером, а также другую информацию о соединении. Мы не будем их рассматривать для экономии места в книге. При необходимости вы найдете описание этих методов в справочной системе Microsoft Visual J++. Приложение CallCGI В 29 томе “Библиотеки системного программиста” мы рассказывали о том, как с помощью расширений сервера Web, выполненных на основе интерфейса CGI и ISAPI можно обрабатывать данные из форм, расположенных в документах HTML. В частности, мы привели там исходные тексты программы controls.exe (составленной на языке программирования С), которая динамически создавала и отображала данные, введенные в форме. Внешний вид этой формы показан на рис. 3.6, воспроизведенном нами из указанного тома. Рис. 3.6. Форма для ввода данных Программа CGI controls.exe получала данные, введенные пользователем в этой форме, после чего динамически создавала документ HTML, в котором отображала состояние переменных серды, полученные данные в исходном и раскодированном виде, а также список значений полей (рис. 3.7). Рис. 3.7. Документ HTML, сформрованный динамически программой CGI control.exe Создавая приложение CallCGI, мы поставили перед собой задачу заменить форму приложением Java, которое вводит с клавиатуры текстовую строку полей и передает ее программе CGI controls.exe. Содержимое динамически сформированного программой CGI документа HTML приложение CallCGI отображает в своем консольном окне, как это показано на рис. 3.8. Рис. 3.8. Отображение в окне приложения Java содержимого документа HTML, полученного от программы CGI Исходный текст приложения CallCGI Исходный текст приложения CallCGI приведен в листинге 3.9. Листинг 3.9. Файл CallCGI\CallCGI.java // ========================================================= // Вызов расширения сервера Web на базе интерфейса CGI // из приложения Java // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class CallCGI { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Размер принятого блока данных int length; // Рабочая строка String str; // Адрес URL вызываемой программы CGI URL u; // Канал связи с расширением CGI URLConnection c; // Выходной поток для передачи данных расширению CGI PrintStream ps; // Входной поток для получения данных от расширения CGI DataInputStream is; try { // Выводим строку приглашения System.out.println("CGI extension call" + "\nEnter any string for send to CGI..."); // Читаем строку, передаваемую расширению, // с клавиатуры length = System.in.read(bKbdInput); // Если строка не пустая, обрабатываем ее if(length != 1) { // Преобразуем строку в формат String str = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); // Выполняем кодировку URL для передаваемой строки String StrEncoded = URLEncoder.encode(str); // Отображаем перекодированную строку System.out.println("Encoded string: >" + StrEncoded + "<"); // Создаем объект класса URL для расширения CGI u = new URL( "http://frolov/frolov-cgi/controls.exe"); // Открываем канал связи с расширением CGI c = u.openConnection(); // Создаем выходной поток данных для передачи // введенной строки серверу CGI ps = new PrintStream(c.getOutputStream()); // Передаем закодированную строку расширению CGI ps.println(StrEncoded); // Закрываем выходной поток ps.close(); // Создаем входной поток для приема данных от // расширения CGI is = new DataInputStream(c.getInputStream()); System.out.println( "\n------------------------------------------" + "\n Data from CGI extension" + "\n------------------------------------------\n"); // Прием данных выполняем в цикле while (true) { // Получаем очередную строку str = is.readLine(); // Если последняя строка, прерываем цикл if(str == null) break; // Отображаем принятую строку System.out.println(str); } // Закрываем входной поток is.close(); } } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста приложения CallCGI Внутри метода main мы определили несколько переменных. Массив bKbdInput предназначен для хранения строки, введенной с помощью клавиатуры. В переменную length записывается длина этой строки. Строка str класса String используется в качестве рабочей. Переменная u класса URL предназначена для хранения ссылки на объект URL, созданный для загрузочного файла программы CGI. Ссылка на канал связи с программой CGI хранится в переменной с именем c класса URLConnection. Переменные ps класса PrintStream и is класса DataInputStream хранят ссылки, соответственно, на выходной и входной потоки, через которые наше приложение обменивается данными с программой CGI. После вывода приглашения на консоль наша программа вводит строку, которая будет передана программе CGI: length = System.in.read(bKbdInput); Далее массив bKbdInput преобразуется в строку str и перекодируется в кодировку URL. Эта кодировка была описана нами в 29 томе “Библиотеки системного программиста”. Она выполняется с помощью статического метода encode, определенного в классе URLEncoder: String StrEncoded = URLEncoder.encode(str); Перекодированная строка отображается на консоли: System.out.println("Encoded string: >" + StrEncoded + "<"); На следующем этапе наше приложение создает объект класса URL для загрузочного файла программы CGI: u = new URL("http://frolov/frolov-cgi/controls.exe"); Здесь предполагается, что программа CGI находится в файле controls.exe, который записан в виртуальный каталог frolov-cgi на сервере Web с адресом http://frolov). Про создание и настройку виртуальных каталогов для размещения расширений сервера Web мы рассказали в 29 томе “Библиотеки системного программиста”. После создания объекта класса URL мы создаем канал с программой CGI как объект класса URLConnection: c = u.openConnection(); Пользуясь этим каналом, мы вначале получаем выходной поток методом getOutputStream, а затем на его базе создаем форматированный выходной поток класса PrintStream, удобный для записи в него текстовых строк: ps = new PrintStream(c.getOutputStream()); Через канал ps наше приложение передает программе CGI строку StrEncoded, а затем закрывает выходной поток, как это показано ниже: ps.println(StrEncoded); ps.close(); В этот момент на сервере Web уже запущена программа CGI, и она приняла переданные ей данные. Обработав эти данные, программа CGI записала в стандартный выходной поток динамически созданный ей документ HTML. Для получения документа наше приложение CallCGI создала входной форматированный поток данных: is = new DataInputStream(c.getInputStream()); Входной поток создан в два приема. Вначале с помощью метода getInputStream приложение создала обычный входной поток, а затем, на его базе, форматированный входной поток класса DataInputStream. Получение от программы CGI динамически сформированного ей документа HTML наше приложение выполняет в цикле по строкам. Строка документа HTML читается из входного форматированного потока методом readLine и записывается в переменную str: str = is.readLine(); Если в процессе чтения был достигнут конец потока, цикл прерывается: if(str == null) break; Строка, полученная методом readLine, отображается на консоли пиложения: System.out.println(str); После завершения цикла входной поток закрывается методом close: is.close(); Исходные тексты программы CGI В лситинге 3.10 мы привели исходный текст программы CGI с именем controls. Он несколько упрощен по сравнению с исходным текстом одноименного приложения, описанного в 29 томе “Библиотеки системного программиста” - мы выбросили обработку метода передачи данных GET, так как наше приложение CallCGI передает данные только методом POST. Описание этой программы вы найдете в упомянутом 29 томе. Листинг 3.10. Файл controls\controls.c // =============================================== // Программа CGI controls.c // Демонстрирует методы получения и обработки // данных от форм, расположенных в документах HTML // // (C) Фролов А.В., 1997 // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // =============================================== #include #include #include // Прототипы функций перекодировки void DecodeStr(char *szString); char DecodeHex(char *str); // ------------------------------------------------ // Функция main // Точка входа программы CGI // ------------------------------------------------ void main(int argc, char *argv[]) { int lSize; FILE * fileReceived; char * szMethod; char szBuf[8196]; char szSrcBuf[8196]; char * szPtr; char * szParam; // Вывод заголовка HTTP и разделительной строки printf("Content-type: text/html\n\n"); // Вывод начального форагмента документа HTML, // формируемого динамически printf(""); printf("Call CGI from Java" ""); // Определяем метод передачи данных szMethod = getenv("REQUEST_METHOD"); // Обработка метода POST if(!strcmp(szMethod, "POST")) { // Определяем размер данных, полученных от навигатора // при передаче данных из полей формы lSize = atoi(getenv("CONTENT_LENGTH")); // Читаем эти данные в буфер szBuf из // стандартного потока ввода STDIN fread(szBuf, lSize, 1, stdin); // Создаем файл, в который будут записаны // принятые данные fileReceived = fopen("received.dat", "w"); // Выполняем запись принятых данных fwrite(szBuf, lSize, 1, fileReceived); // Закрываем файл принятых данных fclose(fileReceived); // Отображаем значения некоторых переменных среды printf("

Environment variables

"); // Метод доступа printf("REQUEST_METHOD = %s", getenv("REQUEST_METHOD")); // Размер полученных данных в байтах printf("
CONTENT_LENGTH = %ld", lSize); // Тип полученных данных printf("
CONTENT_TYPE = %s", getenv("CONTENT_TYPE")); // Закрываем буфер данных двоичным нулем, // превращая его таким образом в строку szBuf[lSize] = '\0'; // Делаем копию принятых данных в буфер szSrcBuf strcpy(szSrcBuf, szBuf); // Отображаем принятые данные без обработки printf("

Received data

"); printf("

%s", szSrcBuf); // Выполняем перекодировку принятых данных DecodeStr(szSrcBuf); // Отображаем результат перекодировки printf("

Decoded data

"); printf("

%s", szSrcBuf); // Выводим список значений полей формы printf("

Filds list

"); // Дописываем в конец буфера принятых данных // символ "&", который используется в качестве // разделителя значений полей szBuf[lSize] = '&'; szBuf[lSize + 1] = '\0'; // Цикл по полям формы for(szParam = szBuf;;) { // Ищем очередной разделитель szPtr = strchr(szParam, '&'); // Если он найден, раскодируем строку параметров if(szPtr != NULL) { *szPtr = '\0'; DecodeStr(szParam); // Выводим в документ значение параметра printf("%s
", szParam); // Переходим к следующему параметру szParam = szPtr + 1; // Если достигнут конец буфера, завершаем цикл if(szParam >= (szBuf + lSize)) break; } else break; } // Выводим завершающий фрагмент документа HTML printf(""); return; } } // ------------------------------------------------ // Функция DecodeStr // Раскодирование строки из кодировки URL // ------------------------------------------------ void DecodeStr(char *szString) { int src; int dst; char ch; // Цикл по строке for(src=0, dst=0; szString[src]; src++, dst++) { // Получаем очередной символ перекодируемой строки ch = szString[src]; // Заменяем символ "+" на пробел ch = (ch == '+') ? ' ' : ch; // Сохраняем результат szString[dst] = ch; // Обработка шестнадцатеричных кодов вида "%xx" if(ch == '%') { // Выполняем преобразование строки "%xx" // в код символа szString[dst] = DecodeHex(&szString[src + 1]); src += 2; } } // Закрываем строку двоичным нулем szString[dst] = '\0'; } // ------------------------------------------------ // Функция DecodeHex // Раскодирование строки "%xx" // ------------------------------------------------ char DecodeHex(char *str) { char ch; // Обрабатываем старший разряд if(str[0] >= 'A') ch = ((str[0] & 0xdf) - 'A') + 10; else ch = str[0] - '0'; // Сдвигаем его влево на 4 бита ch <<= 4; // Обрабатываем младший разряд и складываем // его со старшим if(str[1] >= 'A') ch += ((str[1] & 0xdf) - 'A') + 10; else ch += str[1] - '0'; // Возвращаем результат перекодировки return ch; } 4 РАСТРОВЫЕ ИЗОБРАЖЕНИЯ И АНИМАЦИЯ Одно из наиболее распространенный применений аплетов связано с рисованием простых или анимированных растровых изображений. На серверах Web изображения обычно хранятся в форматах GIF или JPEG. Оба эти формата обеспечивают сжатие изображения, что весьма актуально из-за невысокой скорости передачи данных в сети Internet. Рисование растровых изображений в приложениях для операционной системы Microsoft Windows, составленных на языке программирования С - не простая задача. В классическом программном интерфейсе этой операционной системы отсутствуют функции, с помощью которых можно было бы непосредственно рисовать содержимое файлов с растровыми изображениями. Программист был вынужден работать с заголовками таких файлов, выделять таблицу цветов и биты изображений, создавать и реализовывать палитру, заниматься восстановлением сжатых данных и так далее. Мы описали этот непростой процесс для файлов формата BMP в 14 томе “Библиотеки системного программиста”, который называется “Графический интерфейс GDI в Microsoft Windows”. Разработчик приложений Java находится в намного лучшем положении, так как библиотеки классов Java содержат простые в использовании и мощные средства, предназначенные для работы с растровыми изображениями. В этой главе мы научим вас рисовать в окне аплета содержимое файлов GIF и JPEG, выполняя при необходимости масштабирование, а также создавать на базе таких файлов анимационные изображения, показывая по очереди отдельные кадры небольшого видеофильма. Загрузка и рисование растрового изображения Загрузка растрового изображения из файла выполняется очень просто - с помощью метода getImage, определенного в классе Applet: public Image getImage(URL url); public Image getImage(URL url, String name); Первый вариант метода предполагает использование только одного параметра - адреса URL файла графического изображения. Второй позволяет дополнительно указать относительное расположение файла изображения относительно адреса URL, например: Image img; img = getImage(“http://www.glasnet.ru/~frolov/pic”, "cd.gif"); Если аплет желает загрузить изображение, расположенное в том же каталоге, что и он сам, это можно сделать следующим образом: img = getImage(getCodeBase(), "cd.gif"); Метод getCodeBase, определенный в классе Applet, возвращает адрес URL аплета. Вместо него можно использовать метод getDocumentBase, который также определен в классе Applet и возвращает адрес URL документа HTML, содержащего аплет: img = getImage(getDocumentBase(), "cd.gif"); В любом случае метод getImage создает объект класса Image. Заметим, что на самом деле метод getImage вовсе не загружает изображение через сеть, как это можно было бы подумать. Он только создает объект класса Image. Реальная загрузка файла растрового изображения будет выполняться методом рисования drawImage, который определен в классе Graphics: public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer); public abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer); Как видите, существует четыре варианта этого метода. В качестве первого параметра любому варианту метода передается ссылка на объект класса Image, полученный ранее с помощью метода getImage. Параметры x и y задают координаты верхнего левого угла прямоугольной области, внутри которой будет нарисовано изображение. Эти параметры также задаются для любого варианта метода drawImage. Параметр bgcolor задает цвет фона, на котором будет нарисовано изображение. Как мы говорили в 29 томе “Библиотеки системного программиста”, изображения GIF могут быть прозрачными. В этом случае цвет фона может иметь большое значение. Если для рисования выбраны варианты метода drawImage с параметрами width и height, изображение будет нарисовано с масштабированием. При этом указанные параметры будут определять, соответственно, ширину и высоту изображения. Параметр observer представляет собой ссылку на объект класса ImageObserver, который получит извещение при загрузке изображения. Обычно в качестве такого объекта используется сам аплет, поэтому данный параметр указывается как this. Вот два примера использования метода drawImage: g.drawImage(FloppyDiskImg, 25, 3, this); g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this); В первой строке изображение FloppyDiskImg рисуется в точке с координатами (25, 3) без масштабирования, во второй - в точке с координатами (25, 42), причем высота и ширина нарисованного изображения будет равна 200 пикселам. Метод drawImage запускает процесс загрузки и рисования изображения, а затем, не дожидаясь его завершения, возвращает управление. Так как загрузка файла изображения по сети может отнять немало времени, она выполняется асинхронно в отдельной задаче. Класс Image Как видите, процесс рисования растрового изображения в окне аплета предельно прост - вам достаточно загрузить изображение методом getImage и затем нарисовать его методом drawImage. Но не забывайте, что метод getImage в действительности только создает объект класса Image, но не загружает его. Давайте посмотрим на класс Image. В этом классе имеется единственный конструктор без параметров: public Image(); Вы, однако, скорее всего будете создавать объекты класса Image при помощи метода getImage. Методы getHeight и getWidth, определенные в классе Image, позволяют определить, соответственно, высоту и ширину изображения: public abstract int getHeight(ImageObserver observer); public abstract int getWidth(ImageObserver observer); Так как при вызове этих методов изображение еще может быть не загружено, в качестве параметров методам передается ссылка на объект ImageObserver. Этот объект получит извещение, когда будет доступна высота или ширина изображения. Метод getGraphics позволяет получить так называемый внеэкранный контекст отображения для рисования изображения не в окне аплета, а в оперативной памяти: public abstract Graphics getGraphics(); Эта техника используется для того, чтобы вначале подготовить изображение в памяти, а затем за один прием отобразить его на экране. Еще один метод класса Image, который мы рассмотрим, называется flush: public abstract void flush(); Он освобождает ресурсы, занятые изображением. Приложение ImageDraw Теперь мы знаем все необходимое, чтобы приступить к рисованию растровых изображений в окне аплета. Приложение ImageDraw, о котором мы сейчас расскажем, рисует в своем окне четыре изображения: два изображения флоппи-диска и два - компакт-диска (рис. 4.1). Рис. 4.1. Рисование растровых изображений в окне приложения ImageDraw В верхнем левом углу окна аплета нарисованы исходные изображения. Справа вверху изображение компакт-диска нарисовано растянутым по горизонтали. Нижнюю часть окна аплета занимает пропорционально увеличенный рисунок флоппи-диска. Исходные тексты приложения Исходный текст приложения ImageDraw вы найдете в листинге 4.1. Листинг 4.1. Файл ImageDraw\ImageDraw.java // ========================================================= // Рисование растровых изображений // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class ImageDraw extends Applet { // Изображение флоппи-диска Image FloppyDiskImg; // Изображение компакт-диска Image CDDiskImg; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ImageDraw\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод init, получает управление // при инициализации аплета // ------------------------------------------------------- public void init() { // Загружаем изображение флоппи-диска FloppyDiskImg = getImage(getCodeBase(), "disk.gif"); // Загружаем изображение флоппи-диска CDDiskImg = getImage(getCodeBase(), "cd.gif"); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения белый цвет g.setColor(Color.white); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем увеличенное изображение флоппи-диска g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this); // Рисуем нормальное изображение флоппи-диска g.drawImage(FloppyDiskImg, 25, 3, this); // Рисуем нормальное изображение компакт-диска g.drawImage(CDDiskImg , 70, 3, this); // Рисуем вытянутое изображение компакт-диска g.drawImage(CDDiskImg , 115, 3, 40, 25, this); } } Листинг 4.2 содержит исходный текст документа HTML, созданный для нашего аплета автоматически системой Microsoft Visual J++. Листинг 4.2. Файл ImageDraw\ImageDraw.html ImageDraw

The source. Описание исходных текстов В главном классе нашего аплета определено два поля и несколько методов. Рассмотрим эти поля и самые важные методы. Поля класса ImageDraw В классе ImageDraw определено два поля: Image FloppyDiskImg; Image CDDiskImg; В первом из них хранится ссылка на изображение флоппи-диска, во втором - ссылка на изображение компакт-диска. Метод init Метод init создает два объекта класса Image для файлов disk.gif и cd.gif: FloppyDiskImg = getImage(getCodeBase(), "disk.gif"); CDDiskImg = getImage(getCodeBase(), "cd.gif"); В качестве первого параметра методу getImage передается адрес URL аплета, полученный при помощи метода getCodeBase. При этом предполагается, что файлы disk.gif и cd.gif находятся в том же каталоге, что и аплет. Метод paint После раскрашивания окна аплета в белый цвет и рисования вокруг окна черной рамки метод paint вызывает четыре раза метод drawImage, рисуя изображения флоппи-диска и компакт-диска: g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this); g.drawImage(FloppyDiskImg, 25, 3, this); g.drawImage(CDDiskImg , 70, 3, this); g.drawImage(CDDiskImg , 115, 3, 40, 25, this); В первый раз флоппи-диск рисуется с масштабированием, во второй раз - в исходном виде. Компакт-диск вначале рисуется в исходном виде, а затем - растянутым по горизонтали. Ожидание загрузки изображений Как мы уже говорили, загрузка изображений из сети Internet - длительный процесс, который в среднем идет со скоростью 1 Кбайт в секунду. Поэтому изображения загружаются навигатором в отдельной задаче. При этом метод getImage только создает объект класса Image, а метод drawImage инициирует загрузку изображения и рисует его. Причем если файл изображения имеет большую длину, он будет появляться в окне аплета постепенно по мере загрузки. Однако в некоторых случаях было бы удобно вначале загрузить изображение полностью, а лишь затем выполнять рисование, чтобы изображение появилось на экране сразу. Кроме того, аплет может рисовать сразу несколько изображений в разных местах своего окна или показывать их по очереди в одном и том же месте для достижения эффекта анимации. Есть ли способ определить, когда изображение будет загружено полностью? Есть, и причем целых два. Один из них связан с использованием класса MediaTracker, специально предназначенного для этой цели и достаточно удобного в использовании, другой основан на переопределении одного из методов интерфейса ImageObserver. Применение класса MediaTracker Для того чтобы выполнить ожидание загрузки нескольких изображений, проще воспользоваться классом MediaTracker, а не интерфейсом ImageObserver. Как это сделать? Обычно метод init аплета создает объект класса MediaTracker с помощью конструктора и добавляет в него все изображения, загрузки которых необходимо дождаться. Создание объекта класса MediaTracker Объект класса MediaTracker создается следующим образом: MediaTracker mt; mt = new MediaTracker(this); Конструктору класса MediaTracker передается ссылка на компонент, для которого необходимо отслеживать загрузку изображений. В данном случае это наш аплет, поэтому мы передаем конструктору значение this. Добавление изображений в объект класса MediaTracker Далее метод init должен создать все необходимые объекты класса Image и добавить их в объект MediaTracker методом addImage. Ниже мы показали фрагмент кода, в котором выполняется добавление трех изображений: Image img1; Image img2; Image img3; img1 = getImage(getCodeBase(), "pic1.gif"); img2 = getImage(getCodeBase(), "pic2.gif"); img3 = getImage(getCodeBase(), "pic3.gif"); mt.addImage(img1 , 0); mt.addImage(img2 , 0); mt.addImage(img3 , 0); В качестве первого параметра методу addImage передается ссылка на изображение, загрузку которого необходимо отслеживать, а в качестве второго - идентификатор, который можно будет использовать в процессе отслеживания. Если все, что вам нужно, это дождаться окончания загрузки изображений, то для второго параметра вы можете указать нулевое значение. Ожидание загрузки добавленных изображений Для того чтобы убедиться, что все изображения загружены, вы можете воспользоваться методом waitForAll. Этот метод инициирует загрузку изображений, а также задержит выполнение вызвавшей его задачи до момента полной загрузки всех изображений, добавленных в объект класса MediaTracker: try { mt.waitForAll(); } catch (InterruptedException ex) { } Обратите внимание, что метод waitForAll может создавать исключение InterruptedException. Это исключение возникает, если по какой-либо причине процесс ожидания прерывается. Чаще всего рисование выполняется в отдельной задаче, поэтому метод waitForAll должен вызываться в начале соответствующего метода run. Ниже мы привели исходные тексты приложения ImageDrawWait, в котором такое ожидание выполняется в методе paint, что приводит, однако, к блокировке работы аплета до момента загрузки всех изображений. В данном случае это не критично, так как кроме рисования изображений наш аплет ничего не делает, однако более предпочтительным является выполнение длительных процессов в отдельной задаче. Другие методы класса MediaTracker Какие другие полезные методы, кроме методов addImage и waitForAll есть в классе MediaTracker? public boolean waitForAll(long ms); Метод waitForAll с параметром ms позволяет выполнять ожидание в течение заданного времени. Время ожидания задается в миллисекундах. При этом если за указанное время все изображения были успешно загружены, метод waitForAll возвращает значение true, если нет - false. Вариант метода checkAll с параметром load позволяет проверить, завершилась ли загрузка отслеживаемых изображений: public boolean checkAll(boolean load); Если значение параметра load равно true, метод инициирует загрузку изображений. Если при добавлении изображений методом addImage вы использовали второй параметр этого метода для присваивания разным группам изображений различные идентификаторы, то с помощью метода checkID можно дождаться завершения загрузки отдельной группы изображений: public boolean checkID(int id); Есть также вариант этого метода, позволяющий проверить загрузку группы изображений с заданным идентификатором: public boolean checkID(int id, boolean load); Метод waitForID с параметрами id и ms позволяет выполнять ожидание загрузки группы изображений с заданным идентификатором в течении указанного периода времени: public boolean waitForID(int id, long ms); Класс MediaTracker предоставляет также возможность прослеживать сам процесс загрузки всех добавленных в него изображений или отдельных групп изображений с помощью методов statusAll и statusID: public int statusAll(boolean load); public int statusID(int id, boolean load); В зависимости от значения параметра load эти методы могут инициировать загрузку изображений. Если параметр равен true, загрузка изображений инициируется, если false - выполняется только проверка текущего состояния загрузки. Методы statusAll и statusID возвращают значение, составленное из отдельных битов состояния при помощи логической операции ИЛИ. Ниже мы перечислили эти биты состояния и привели их краткое описание. Биты состоянияОписаниеMediaTracker.LOADINGОдин или несколько отслеживаемых файлов продолжают загружатьсяMediaTracker.ABORTEDЗагрузка одного или нескольких файлов была прерванаMediatTracker.ERROREDПри загрузке одного или нескольких файлов произошла ошибкаMediaTracker.COMPLETEЗагрузка всех отслеживаемых файлов произошла полностью и успешно Еще четыре метода, определенных в классе MediaTracker, связаны с обработкой ошибок: public boolean isErrorAny(); public boolean isErrorID(int id); public Object[] getErrorsAny(); public Object[] getErrorsID(int id); Методы isErrorAny и isErrorID позволяют проверить, возникла ли ошибка при загрузке, соответственно, любого из отслеживаемых изображений или изображений из заданной группы. Если ошибка произошла, возвращается значение true, если нет - значение false. Методы getErrorsAny и getErrorsID возвращают массив объектов, при ожидании загрузки которых произошла ошибка. Первый из этих методов возвращает массив для всех отслеживаемых объектов, второй - только объектов из заданной группы. Применение интерфейса ImageObserver Второй способ ожидания завершения процесса загрузки изображений связан с интерфейсом ImageObserver. Определение этого интерфейса не занимает много места, поэтому мы приведем его полностью: public interface java.awt.image.ImageObserver { // ------------------------------------------------------- // Биты флагов для параметра infoflags метода imageUpdate // ------------------------------------------------------- public final static int ABORT; public final static int ALLBITS; public final static int ERROR; public final static int FRAMEBITS; public final static int HEIGHT; public final static int PROPERTIES; public final static int SOMEBITS; public final static int WIDTH; // ------------------------------------------------------- // Метод imageUpdate // ------------------------------------------------------- public abstract boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height); } Как видите, в интерфейсе ImageObserver определен единственный метод imageUpdate и набор битовых флагов для этого метода. Класс Component, от которого происходит класс Applet, реализует интерфейс ImageObserver: public abstract class java.awt.Component extends java.lang.Object implements java.awt.image.ImageObserver { . . . } Этот интерфейс используется для отслеживания процесса загрузки и перерисовки изображений и других компонент, расположенных внутри компонента. В частности, он используется для отслеживания загрузки и рисования растровых изображений в окне аплета, чем мы и воспользуемся. В процессе загрузки вызывается метод imageUpdate, поэтому чтобы отслеживать загрузку изображений, наш аплет должен переопределить этот метод. Процедура ожидания загрузки изображений достаточно проста. Прежде всего, аплет должен передать в последнем параметре методу drawImage ссылку на интерфейс ImageObserver, который будет применяться для отслеживания процесса загрузки: g.drawImage(Img, x, y, width, height, this); Здесь в качестве ссылки на интерфейс ImageObserver мы передали значение this. При этом будет применен интерфейс нашего аплета. Соответственно, нам нужно определить в классе аплета метод imageUpdate, который будет вызываться в процессе загрузки изображений. Ниже мы привели возможный вариант реализации этого метода, который позже будет описан в деталях: public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) { // Проверяем, все ли биты изображения загружены fAllLoaded = ((flags & ALLBITS) != 0); // Если все, перерисовываем окно if(fAllLoaded) repaint(); // Если все биты загружены, дальнейшие вызовы // метода imageUpdate не нужны return !fAllLoaded; } Через первый параметр img методу imageUpdate передается ссылка на изображение, загрузка которого отслеживается. Параметр flags отражает состояние процесса загрузки. Через остальные параметры x, y, w и h передаются, соответственно, координаты и размеры изображения. Основное, что должен делать метод imageUpdate для отслеживания процесса загрузки - это проверять флаги flags, дожидаясь установки нужных флагов. Флаги определены следующим образом: public final static int WIDTH; public final static int HEIGHT = 2; public final static int PROPERTIES = 4; public final static int SOMEBITS = 8; public final static int FRAMEBITS = 16; public final static int ALLBITS = 32; public final static int ERROR = 64; public final static int ABORT = 128; Ниже мы привели краткое описание перечисленных выше флагов. ФлагОписаниеWIDTHИзображение загружено настолько, что стала доступна его ширина. Значение ширины изображения можно получить из параметра w метода imageUpdate HEIGHTАналогично предыдущему, но для высоты изображения. Высоту изображения можно получить из параметра h метода imageUpdatePROPERTIESСтали доступны свойства изображения, которые можно получить методом getProperty класса Image. В нашей книге мы опустили описание этого методаSOMEBITSСтали доступны биты изображения для рисования в масштабе. Через параметры x, y, h и w передаются координаты и размеры прямоугольной области, которая ограничивает загруженную часть изображенияFRAMEBITSЗагружен очередной фрейм изображения, состоящего из нескольких фреймов. Параметры x, y, h и w следует игнорироватьALLBITS Изображение загружено полностью. Параметры x, y, h и w следует игнорировать ERRORПри загрузке произошла ошибкаABORTЗагрузка изображения была прервана или отменена Анализируя состояние флагов, метод imageUpdate может следить за ходом загрузки изображений, отображая, например, процент завершения процесса загрузки или выполняя какие-либо другие действия. Если вам нужно только дождаться завершения процесса загрузки, достаточно использовать флаг ALLBITS. Для проверки ошибок воспользуйтесь флагами ERROR и ABORT. Приложение ImageDrawWait В приложении ImageDrawWait мы демонстрируем использование класса MediaTracker для ожидания процесса завершения загрузки изображений, показанных на рис. 4.1. Эти изображения рисуются приложением ImageDrawWait не на белом фоне, а на фоне другого изображения. Файл изображение фона имеет значительный размер, поэтому без применения техники ожидания завершения загрузки он будет появляться в окне по частям (именно так работает метод imageUpdate, определенный в классе Applet), а затем будут нарисованы остальные изображения. Наше приложение сначала дожидается завершения загрузки всех изображений, а затем рисует их, поэтому все изображения появятся практически одновременно и целиком. В процессе загрузки окно приложения ImageDrawWait отображает сообщение о ходе загрузки (рис. 4.2). Рис. 4.2. Сообщение о ходе процесса загрузки изображений После загрузки в окне аплета рисуется изображение фона Исходные тексты приложения Исходные тексты приложения ImageDrawWait вы найдете в листинге 4.3. Листинг 4.3. Файл ImageDrawWait\ImageDrawWait.java // ========================================================= // Рисование растровых изображений с ожиданием их загрузки // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class ImageDrawWait extends Applet { // Фоновое изображение Image BkgImg; // Изображение флоппи-диска Image FloppyDiskImg; // Изображение компакт-диска Image CDDiskImg; // Ссылка на MediaTracker MediaTracker mt; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: ImageDrawWait\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод init, получает управление // при инициализации аплета // ------------------------------------------------------- public void init() { // Создаем объект класса MediaTracker mt = new MediaTracker(this); // Загружаем фоновое изображение BkgImg = getImage(getCodeBase(), "bkg.gif"); // Добавляем его в список объекта MediaTracker mt.addImage(BkgImg , 0); // Загружаем изображение флоппи-диска FloppyDiskImg = getImage(getCodeBase(), "disk.gif"); // Добавляем его в список объекта MediaTracker mt.addImage(FloppyDiskImg, 0); // Загружаем изображение флоппи-диска CDDiskImg = getImage(getCodeBase(), "cd.gif"); // Добавляем его в список объекта MediaTracker mt.addImage(CDDiskImg, 0); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения белый цвет g.setColor(Color.white); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рисуем сообщение о начале загрузки g.drawString("Подождите, идет загрузка...", 20, dimAppWndDimension.height / 2); // Ждем, пока все изображения не будут загружены try { mt.waitForAll(); } catch (InterruptedException ex) { } // Рисуем изображение фона g.drawImage(BkgImg, 1, 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 2, this); // Рисуем увеличенное изображение флоппи-диска g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this); // Рисуем нормальное изображение флоппи-диска g.drawImage(FloppyDiskImg, 25, 3, this); // Рисуем нормальное изображение компакт-диска g.drawImage(CDDiskImg , 70, 3, this); // Рисуем вытянутое изображение компакт-диска g.drawImage(CDDiskImg , 115, 3, 40, 25, this); } } В листинге 4.4 приведен исходный текст документа HTML, созданный для нашего аплета. Листинг 4.4. Файл ImageDrawWait\ImageDrawWait.html ImageDrawWait

The source. Описание исходных текстов Опишем наиболее важные методы приложения ImageDrawWait. Метод init Прежде всего метод init создает объект класса MediaTracker, который будет использоваться для отслеживания процесса загрузки изображений: mt = new MediaTracker(this); Далее метод init последовательно создает три объекта класса Image (соответственно, для изображений фона, для флоппи-диска и для компакт-диска), а затем добавляет их в объект MediaTracker с помощью метода addImage: BkgImg = getImage(getCodeBase(), "bkg.gif"); mt.addImage(BkgImg , 0); FloppyDiskImg = getImage(getCodeBase(), "disk.gif"); mt.addImage(FloppyDiskImg, 0); CDDiskImg = getImage(getCodeBase(), "cd.gif"); mt.addImage(CDDiskImg, 0); Метод paint Метод paint прежде всего раскрашивает окно аплета в белый цвет и обводит его черной рамкой. Затем на подготовленной таким образом поверхности он пишет сообщение о начале процесса загрузки изображений: g.drawString("Подождите, идет загрузка...", 20, dimAppWndDimension.height / 2); Далее ожидается загрузка всех изображений, для чего вызывается метод waitForAll из класса MediaTracker: try { mt.waitForAll(); } catch (InterruptedException ex) { } Когда все изображения будут загружены, следует серия вызовов метода drawImage, с помощью которых рисуется изображение фона, два изображения флоппи-диска и два изображения компакт-диска: g.drawImage(BkgImg, 1, 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 2, this); g.drawImage(FloppyDiskImg, 25, 42, 200, 200, this); g.drawImage(FloppyDiskImg, 25, 3, this); g.drawImage(CDDiskImg , 70, 3, this); g.drawImage(CDDiskImg , 115, 3, 40, 25, this); Приложение DrawImageObserver Приложение DrawImageObserver рисует в своем окне изображение фона, такое же, как и в предыдущем приложении. При этом для ожидания процесса загрузки изображения перед рисованием фона мы используем интерфейс ImageObserver. Если бы изображение фона, имеющее значительный размер, рисовалось без ожидания его загрузки, оно появлялось бы в окне аплета по частям. Наше приложение рисует его сразу, так как дожидается полной загрузки. Исходные тексты приложения Главный файл исходных текстов приложения DrawImageObserver приведен в листинге 4.5. Листинг 4.5. Файл DrawImageObserver\DrawImageObserver.java // ========================================================= // Рисование растровых изображений с ожиданием их загрузки // Для ожидания применяется интерфейс ImageObserver // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class DrawImageObserver extends Applet { // Фоновое изображение Image BkgImg; boolean fAllLoaded = false; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: DrawImageObserver\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Метод init, получает управление при // инициализации аплета // ------------------------------------------------------- public void init() { // Загружаем фоновое изображение BkgImg = getImage(getCodeBase(), "bkg.gif"); } // ------------------------------------------------------- // imageUpdate // Вызывается, когда появляется информация об изображении // ------------------------------------------------------- public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) { // Проверяем, все ли изображение загружено fAllLoaded = ((flags & ALLBITS) != 0); // Если все, перерисовываем окно if(fAllLoaded) repaint(); // Если изображение загружено полностью, дальнейшие // вызовы метода imageUpdate не нужны return !fAllLoaded; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения белый цвет g.setColor(Color.white); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); if(fAllLoaded == false) { // Рисуем сообщение о начале загрузки g.drawString("Подождите, идет загрузка...", 20, dimAppWndDimension.height / 2); } // Рисуем изображение фона g.drawImage(BkgImg, 1, 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 2, this); } } В листинге 4.6 вы найдете исходный текст документа HTML, созданного автоматически для нашего аплета. Листинг 4.6. Файл DrawImageObserver\DrawImageObserver.html DrawImageObserver

The source. Описание исходных текстов Наше приложение переопределяет метод imageUpdate, в котором отслеживает процесс загрузки фонового изображения. Опишем основные методы, определенные в приложении DrawImageObserver. Метод init В процессе инициализации аплета метод init создает объект класса Image, соответствующий изображению фона: public void init() { BkgImg = getImage(getCodeBase(), "bkg.gif"); } Как вы уже знаете, при этом реальная загрузка файла изображения не выполняется. Метод paint Свою работу метод paint начинает с раскрашивания окна аплета и рисования рамки вокруг окна. Затем метод проверяет флаг fAllLoaded, начальное значение которого равно false: if(fAllLoaded == false) { g.drawString("Подождите, идет загрузка...", 20, dimAppWndDimension.height / 2); } Флаг fAllLoaded служит индикатором полной загрузки изображения и устанавливается методом imageUpdate, отслеживающим загрузку. Пока значение этого флага равно false, метод paint отображает в окне аплета сообщение о том, что идет процесс загрузки. Когда изображение будет полностью загружено, метод imageUpdate устанавливает значение флага fAllLoaded, равное true, а затем принудительно перерисовывает окно аплета, вызывая метод repaint. При этом метод paint рисует в окне аплета полностью загруженное изображение фона: g.drawImage(BkgImg, 1, 1, dimAppWndDimension.width - 2, dimAppWndDimension.height - 2, this); Метод imageUpdate Метод imageUpdate периодически вызывается в процессе загрузки изображения, конструируя каждый раз значение флага полной загрузки fAllLoaded следующим образом: fAllLoaded = ((flags & ALLBITS) != 0); Когд изображение будет полностью загружено, в параметре flags метода imageUpdate будет установлен флаг ALLBITS, после чего флаг fAllLoaded будет установлен в значение true. Как только это произойдет, метод imageUpdate вызовет метод repaint, выполнив принудительную перерисовку окна аплета: if(fAllLoaded) repaint(); При этом метод paint нарисует в окне аплета изображение фона, закрасив им сообщение о ходе процесса загрузки изображения. Метод imageUpdate должен возвратить значение false или true. Если изображение еще не загружено, возвращается значение true: return !fAllLoaded; При этом метод imageUpdate будет вызываться еще раз для отслеживания процесса загрузки. Когда загрузка будет завершена, метод imageUpdate возвратит значение false, после чего этот метод вызываться больше не будет. Видео в окне аплета Наиболее динамичные страницы сервера Web содержат анимационные изображения в виде небольших видеофильмов. Как мы рассказывали в 29 томе “Библиотеки системного программиста”, который называется “Сервер Web своими руками”, вы можете подготовить видеофильм как файл AVI или как многосекционный файл GIF. Файл AVI представляет собой многопоточный файл, содержащий видео и звук. О том, как создавать такие файлы, мы рассказали в 15 томе “Библиотеки системного программиста” с называнием “Мультимедиа для Windows”. Файлы AVI можно создавать при помощи специального видеоадаптера, который способен оцифровывать сигнал с видеокамеры или видеомагнитофона, а также из отдельных изображений, составляющих кадры видеофильма. Заметим, однако, что озвученный видеофильм в формате AVI продолжительностью в 1 минуту занимает мегабайты дискового пространства. При существующих на сегодняшний день скоростях передачи данных через Internet не имеет никакого смысла размещать на страницах сервера Web такие файлы. Многосекционные файлы GIF не содержат звуковой информации и состоят обычно из одного-двух десятков кадров. Для каждого такого кадра вы можете задавать время отображения и координаты, где этот кадр будет отображаться. Можно также добиться зацикленного отображения видеофильма, созданного как многосекционный файл GIF. Аплеты Java предоставляют вам еще одну возможность отображения небольших видеофильмов на страницах сервера Web. Для реализации этой возможности вы должны подготовить и разместить в одном из каталогов сервера Web файлы отдельных кадров видеофильма в формате GIF или JPEG. Аплет Java должен загрузить эти изображения, дождавшись окончания процесса загрузки, что можно сделать либо при помощи рассмотренного в этой главе класса MediaTracker либо при помощи интерфейса ImageObserver. Как только все изображения будут полностью загружены, аплет может начинать их поочередное отображение в цикле. Этот цикл должен выполняться в отдельной задаче. Так как аплет полностью контролирует отображение кадров фильма, он может реализовывать эффекты, недостижимые при использовании файлов AVI или многосекционных файлов GIF. Например, аплет может накладывать или смешивать кадры различных фильмов, рисовать поверх кадров произвольные изображения или делать надписи, масштабировать отдельные фрагменты кадров или весь кадр и так далее. Здесь все ограничивается главным образом вашей фантазией. Так как мы уже научились выполнять все необходимые для показа видеофильма операции, перейдем сразу к исходным текстам приложения CDRotation. Приложение CDRotation Задача отображения видеофильмов в окне Java настолько важна, что Microsoft включил в Visual J++ специальные средства для создания шаблона исходных текстов аплета с анимацией. Если на третьем шаге системы автоматизированной создания исходных текстов аплетов Java Applet Wizard включить переключатель Yes в поле Would you like your applet to be multi-threaded, а также переключатель Yes в поле Would you like support for animation (рис. 4.3), для вас будут созданы исходные тексты аплета, в окне которого находится изображение земного шара, вращающегося вдоль вертикальной оси. Рис. 4.3. Включение исходного текста для работы с анимацией в создаваемый аплет Все, что вам остается сделать, это изменить созданные для вас исходные тексты таким образом, чтобы они соответствовали вашим потребностям. Именно так мы создали исходные тексты приложения CDRotation, в окне которого изображается вращающийся компакт-диск. Когда будете запускать приложение CDRotation, обратите внимание, что в левом верхнем углу каждого кадра отображается его порядковый номер. Этот номер не нарисован в файлах кадров, а надписывается приложением после рисования очередного кадра. Такое невозможно, если располагать в документе HTML файл AVI или многосекционный файл GIF. Исходные тексты приложения Главный файл исходных текстов приложения CDRotation представлен в листинге 4. 7. Листинг 4.7. Файл CDRotation\CDRotation.java // ========================================================= // Рисование вращающегося компакт-диска // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class CDRotation extends Applet implements Runnable { // Ссылка на задачу рисования // вращающегося компакт-диска Thread m_CDRotation = null; // Контекст отображения для рисования private Graphics m_Graphics; // Массив изображений компакт-диска private Image m_Images[]; // Номер текущего изображения private int m_nCurrImage; // Ширина изображения private int m_nImgWidth = 0; // Высота изображения private int m_nImgHeight = 0; // Флаг загрузки всех изображений private boolean m_fAllLoaded = false; // Общее количество изображений private final int NUM_IMAGES = 11; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: CDRotation\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // displayImage // Рисование текущего изображения, если все изображения // уже загружены // ------------------------------------------------------- private void displayImage(Graphics g) { // Если не все изображения загружены, // ничего не делаем if (!m_fAllLoaded) return; // Рисуем текущее изображение в центре окна аплета g.drawImage(m_Images[m_nCurrImage], (size().width - m_nImgWidth) / 2, (size().height - m_nImgHeight) / 2, null); // Рисуем в вернем левом углу кадра его порядковый номер g.drawString((new Integer(m_nCurrImage)).toString(), (size().width - m_nImgWidth) / 2, ((size().height - m_nImgHeight) / 2) + 10); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения белый цвет g.setColor(Color.white); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Если все изображения загружены, рисуем // текущее изображение if (m_fAllLoaded) { displayImage(g); } // Если не загружены, рисуем сообщение // о загрузке else g.drawString("Подождите, идет загрузка...", 10, dimAppWndDimension.height / 2); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { if (m_CDRotation == null) { m_CDRotation = new Thread(this); m_CDRotation.start(); } } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { if (m_CDRotation != null) { m_CDRotation.stop(); m_CDRotation = null; } } // ------------------------------------------------------- // run // Метод, который работает в рамках отдельной задачи // Он рисует в окне аплета изображения - кадры // клипа "вращающийся компакт-диск" // ------------------------------------------------------- public void run() { // Инициализируем номер текущего изображения m_nCurrImage = 0; // Проверяем, все ли изображения загружены. // Если нет, загружаем их if (!m_fAllLoaded) { // Перерисовываем окно аплета repaint(); // Получаем контекст отображения для окна m_Graphics = getGraphics(); // Создаем массив изображений m_Images = new Image[NUM_IMAGES]; // Создаем объект MediaTracker для контроля // загружки изображений MediaTracker tracker = new MediaTracker(this); // Переменная для хранения имени файла изображения String strImage; // Цикл загрузки изображений for (int i = 0; i < NUM_IMAGES; i++) { // Записываем в строку strImage имя текущего файла // с изображением strImage = "images/cdimg0" + ((i < 10) ? "0" : "") + i + ".gif"; // Инициируем получение изображения m_Images[i] = getImage(getDocumentBase(), strImage); // Добавляем изображение в объект MediaTracker tracker.addImage(m_Images[i], 0); } // Ожидаем окончание загрузки всех изображений try { tracker.waitForAll(); // Если не было ошибок, устанавливаем флаг // окончания загрузки m_fAllLoaded = !tracker.isErrorAny(); } catch (InterruptedException e) { } // Если при загрузке изображений произошла ошибка, // останавливаем задачу и рисуем сообщение об // ошибке if (!m_fAllLoaded) { stop(); m_Graphics.drawString( "При загрузке изображений произошла ошибка", 10, size().height / 2); return; } // Сохраняем ширину и высоту первого изображения m_nImgWidth = m_Images[0].getWidth(this); m_nImgHeight = m_Images[0].getHeight(this); } // Перерисовываем окно аплета repaint(); // Запускаем цикл рисования изображений while (true) { try { // Рисуем текущее изображение displayImage(m_Graphics); // Увеличиваем номер текущего изображения m_nCurrImage++; // Если достигли максимального номера, // начинаем с самого начала if(m_nCurrImage == NUM_IMAGES) m_nCurrImage = 0; // Выполняем задержку в 30 миллисекунд Thread.sleep(30); } // Если в процессе рисования возникло // исключение, останавливаем задачу catch (InterruptedException e) { stop(); } } } } Листинг 4.8 содержит исходный текст документа HTML, созданного для аплета CDRotation. Листинг 4.8. Файл CDRotation\CDRotation.html CDRotation

The source. Описание исходных текстов Рассмотрим наиболее важные методы нашего приложения. Метод start В задачу метода start, который получает управление при отображении окна аплета, входит создание и запуск задачи, отображающий кадры видеофильма с изображением вращающегося компакт-диска: if (m_CDRotation == null) { m_CDRotation = new Thread(this); m_CDRotation.start(); } Задача создается как объект класса Thread, причем конструктору передается ссылка на главный класс аплета. Поэтому при запуске задачи управление получит метод run, определенный в классе аплета. Метод stop Метод stop останавливает работу задачи, когда окно аплета исчезает с экрана: if(m_CDRotation != null) { m_CDRotation.stop(); m_CDRotation = null; } Для остановки вызывается метод stop. Метод paint Сразу после получения управления, метод paint закрашивает окно аплета белым цветом и рисует вокруг него черную рамку. Затем метод проверяет содержимое флага m_fAllLoaded. Этот флаг установлен в значение true, когда все кадры видеофильма загружены и сброшен в значение false, когда загрузка кадров еще не завершена. Последняя ситуация возникает всегда при первом вызове метода paint. Если все изображения загружены, метод paint вызывает метод displayImage, определенный в нашем приложении: if(m_fAllLoaded) { displayImage(g); } Этот метод, о котором мы еще расскажем подробнее, отображает в окне аплета текущий кадр видеофильма. Если же кадры видеофильма еще не загружены, в окне аплета отображается соответствующее сообщение: else g.drawString("Подождите, идет загрузка...", 10, dimAppWndDimension.height / 2); Метод run Метод run работает в рамках отдельной задачи. Он занимается последовательным рисованием кадров нашего видеофильма. Прежде всего метод run записывает нулевое значение в поле m_nCurrImage, хранящее номер текущего отображаемого кадра: m_nCurrImage = 0; Далее выполняется проверка, загружены ли все кадры видеофильма, для чего анализируется содержимое флага m_fAllLoaded. Если изображения не загружены (а в самом начале так оно и есть) метод run перерисовывает окно аплета и получает контекст отображения для этого окна. Затем создается массив объектов Image для хранения кадров видеофильма: m_Images = new Image[NUM_IMAGES]; Метод run создает также объект класса MediaTracker для ожидания загрузки всех кадров видеофильма: MediaTracker tracker = new MediaTracker(this); Далее метод run в цикле загружает изображения и добавляет их в объект класса MediaTracker для того чтобы можно было дождаться загрузки всех кадров: for (int i = 0; i < NUM_IMAGES; i++) { strImage = "images/cdimg0" + ((i < 10) ? "0" : "") + i + ".gif"; m_Images[i] = getImage(getDocumentBase(), strImage); tracker.addImage(m_Images[i], 0); } Здесь предполагается, что файлы изображений находятся в каталоге images, который, в свою очередь, размещен там же, где и двоичный файл аплета. Имена файлов, составляющих отдельные кадры, начинаются с префикса cdimg0, вслед за которым идет номер кадра (00, 01, 02, и так далее), и расширение имени .gif. Ожидание загрузки кадров выполняется с помощью метода waitForAll, о котором мы вам уже рассказывали: try { tracker.waitForAll(); m_fAllLoaded = !tracker.isErrorAny(); } catch (InterruptedException e) { } После окончания ожидания флаг завершения загрузки устанавливается только в том случае, если метод isErrorAny вернул значение false, то есть если не было никаких ошибок. Если же произошла ошибка, в окне аплета отображается соответствующее сообщение, после чего работа метода run (и, следовательно, работа созданной для него задачи) заканчивается: if(!m_fAllLoaded) { stop(); m_Graphics.drawString( "При загрузке изображений произошла ошибка", 10, size().height / 2); return; } В случае удачной загрузки всех кадров метод run получает ширину и высоту первого кадра видеофильма и сохраняет эти значения в переменных m_nImgWidth и m_nImgHeight: m_nImgWidth = m_Images[0].getWidth(this); m_nImgHeight = m_Images[0].getHeight(this); Далее окно аплета перерисовывается: repaint(); При этом метод paint отображает в окне аплета первый кадр видеофильма. На следующем этапе работы метода run запускается цикл отображения кадров фильма: while (true) { try { displayImage(m_Graphics); m_nCurrImage++; if(m_nCurrImage == NUM_IMAGES) m_nCurrImage = 0; Thread.sleep(30); } catch (InterruptedException e) { stop(); } } В этом бесконечном цикле вызывается метод displayImage, рисующий текущий кадр видеофильма, после чего номер текущего кадра увеличивается на единицу. Если показаны все кадры, номер текущего кадра становится равным нулю, а затем процесс продолжается. Между отображением кадров выполняется задержка величиной 30 миллисекунд. Метод displayImage Метод displayImage вызывается из двух мест - из метода paint при перерисовке окна аплета и из метода run (периодически). Если кадры видеофильма не загружены, содержимое флага m_fAllLoaded равно false и метод displayImage просто возвращает управление, ничего не делая: if(!m_fAllLoaded) return; Если же загрузка изображений завершена, этот метод рисует в центре окна текущий кадр видеофильма, вызывая для этого знакомый вам метод drawImage: g.drawImage(m_Images[m_nCurrImage], (size().width - m_nImgWidth) / 2, (size().height - m_nImgHeight) / 2, null); После того как кадр нарисован, мы надписываем на нем его порядковый номер, вызывая для этого метод drawString: g.drawString((new Integer(m_nCurrImage)).toString(), (size().width - m_nImgWidth) / 2, ((size().height - m_nImgHeight) / 2) + 10); 5 ЗВУК В АПЛЕТАХ JAVA Нельзя сказать, что звуковые возможности аплетов Java чрезмерно велики. Скорее наоборот, они минимальны. Тем не менее, аплеты могут проигрывать звуковые клипы, записанные в файлах формата AU, который пришел из мира компьютеров фирмы Sun. Сказанное, однако, не означает, что если у вас нет рабочей станции Sun, то вы не сможете озвучить свои аплеты. Во-первых, в сети Internet можно найти много готовых звуковых файлов AU, а во-вторых, там же есть программы для преобразования форматов звуковых файлов. Одну из таких условно-бесплатных программ, которая называется GoldWave, вы можете загрузить с сервера ftp. winsite.com. Загрузка и проигрывание звуковых файлов Работа со звуковыми файлами во многом напоминает работу с растровыми графическими файлами. Вначале вы должны получить ссылку на интерфейс AudioClip, а затем, пользуясь его методами, вы сможете выполнять проигрывание содержимого этого файла. Для получения интерфейса AudioClip вы должны воспользоваться одним из двух вариантов метода getAudioClip, определенных в классе Applet: public AudioClip getAudioClip(URL url): public AudioClip getAudioClip(URL url, String name); Первый вариант метода предполагает указание адреса URL звукового файла через единственный параметр, второй допускает раздельное указание адреса URL каталога, содержащего файл, и имени файла. В документации на метод getAudioClip сказано, что этот метод фактически не выполняет загрузку звуковых данных, а только возвращает ссылку на интерфейс AudioClip и немедленно возвращает управление. Загрузка звуковых данных выполняется методами, предназначенными для проигрывания файла. Однако в книге “The Java Tutorial. Object-Oriented Programming for the Internet”, подготовленной специалистами группы JavaSoft, утверждается, что текущие реализации Java работают по другому: метод getAudioClip возвращает управление только после завершения загрузки звукового файла. Очевидно, вам не стоит полагаться на то, что так будет всегда. В тех случаях, когда нежелательно блокирование работы аплета на время загрузки звукового файла, загрузку и проигрывание следует выполнять в отдельной задаче. Интерфейс AudioClip определен следующим образом: public interface java.applet.AudioClip { public abstract void play(); public abstract void loop(); public abstract void stop(); } Метод play запускает однократное проигрывание звукового файла, которое выполняется от начала файла и до его конца. Метод loop запускает проигрывание звукового файла в цикле, которое будет продолжаться до тех пор, пока вы не остановите его, вызвав метод stop. Метод stop, как нетрудно догадаться из его названия, останавливает проигрывание звукового файла, как однократное, так и выполняемое в цикле. Приложение Audio Приложение Audio демонстрирует использование интерфейса AudioClip. В его окне (рис. 5.1) имеются три кнопки с названиями Play, Loop и Stop. Рис. 5.1. Окно аплета Audio Сразу после запуска аплета кнопка Stop находится в заблокированном состоянии. Если нажать кнопку Play или Loop, начнется, соответственно, однократное проигрывание или проигрывание в цикле файла с именем kaas.au, распложенного в том же каталоге, что и двоичный файл аплета Audio. Когда начинается проигрывание звукового файла, кнопка Stop разблокируется, что позволяет остановить проигрывание. Исходные тексты приложения Основной файл исходных текстов приложения приведен в листинге 5.1. Листинг 5.1. Файл Audio\ Audio.java // ========================================================= // Проигрывание аудиофайла // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; public class Audio extends Applet { // ------------------------------------------------------- // Поля класса. // Создаются автоматически для всех параметров аплета // ------------------------------------------------------- // Строка для хранения имени аудиофайла private String m_ClipName = "kaas.au"; // ------------------------------------------------------- // Имена параметров // ------------------------------------------------------- // Строка имени параметра private final String PARAM_ClipName = "ClipName"; // Аудиоклип AudioClip auClip; // Кнопка для однократного проигрывания Button btPlay; // Кнопка для проигрывания в цикле Button btLoop; // Кнопка для остановки проигрывания Button btStop; // Флаг проигрывания в цикле boolean fLoopPlay = false; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Audio\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // getParameterInfo // Метод, возвращающий описание параметров // ------------------------------------------------------- public String[][] getParameterInfo() { String[][] info = { { PARAM_ClipName, "String", "Audioclip filename" }, }; return info; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Рабочая переменная для получения параметров String param; // Получение параметров и сохранение // их значений в полях класса // Получение имени аудиофайла param = getParameter(PARAM_ClipName); if (param != null) m_ClipName = param; // Создаем кнопку для однократного проигрывания btPlay = new Button("Play"); // Создаем кнопку для проигрывания в цикле btLoop = new Button("Loop"); // Создаем кнопку для остановки проигрывания btStop = new Button("Stop"); // Блокируем эту кнопку, так как пока еще // проигрывание не запущено btStop.disable(); // Добавляем кнопки в окно аплета add(btPlay); add(btLoop); add(btStop); // Создаем аудиоклип как объект класса AudioClip auClip = Applet.getAudioClip(getCodeBase(), m_ClipName); } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Выполняем ветвление по кнопкам // Нажата кнопка однократного проигрывания if(evt.target.equals(btPlay)) { // Запускаем однократное проигрывание auClip.play(); // Разблокируем кнопку остановки проигрывания btStop.enable(); } // Нажата кнопка проигрывания в цикле else if(evt.target.equals(btLoop)) { // Запускаем проигрывание в цикле auClip.loop(); // Устанавливаем флаг проигрывания в цикле fLoopPlay = true; // Разблокируем кнопку остановки проигрывания btStop.enable(); } // Нажата кнопка остановки проигрывания else if(evt.target.equals(btStop)) { // Останавливаем проигрывание auClip.stop(); // Сбрасываем флаг проигрывания в цикле fLoopPlay = false; // Блокируем кнопку остановки проигрывания btStop.disable(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } // ------------------------------------------------------- // start // Метод вызывается при первом отображении окна аплета // ------------------------------------------------------- public void start() { // Если установлен флаг проигрывания в цикле, // запускаем такое проигрывание if(fLoopPlay) auClip.loop(); } // ------------------------------------------------------- // stop // Метод вызывается, когда окно аплета исчезает с экрана // ------------------------------------------------------- public void stop() { // Если установлен флаг проигрывания в цикле, // останавливаем проигрывание if(fLoopPlay) auClip.stop(); } } В листинге 5.2 вы найдете исходный текст документа HTML, созданного автоматически для нашего приложения системой Microsoft Visual J++. Листинг 5.2. Файл Audio\ Audio.html Audio

The source. Описание исходного текста В главном классе аплета определено несколько полей и методов. Рассмотрим эти поля и наиболее важные методы. Поля класса Audio В поле m_ClipName хранится имя звукового файла, которое передается через параметр ClipName из документа HTML. По умолчанию для этого параметра используется значение kaas.au. Строка PARAM_ClipName хранит имя указанного выше параметра. Ссылка на интерфейс AudioClip хранится в поле auClip: AudioClip auClip; Следующие три поля хранят ссылки на кнопки, предназначенные для управления проигрыванием звукового файла: Button btPlay; Button btLoop; Button btStop; Поле fLoopPlay типа boolean используется для флага, которым отмечается режим проигрывания звукового файла в цикле. Метод getParameterInfo Метод getParameterInfo возвращает описание единственного параметра нашего аплета, через который передается имя звукового файла. Метод init Сразу после запуска аплета метод init получает значение параметра - имя звукового файла, и если этот параметр задан в документе HTML, записывает полученное имя в поле m_ClipName: param = getParameter(PARAM_ClipName); if(param != null) m_ClipName = param; Далее создаются три кнопки, управляющие звучанием аплета: btPlay = new Button("Play"); btLoop = new Button("Loop"); btStop = new Button("Stop"); Кнопка Stop блокируется, так как на данный момент проигрывание еще не запущено: btStop.disable(); Для блокирования вызывается метод disable, определенный в классе Button. Подготовленные таким образом кнопки добавляются в окно аплета: add(btPlay); add(btLoop); add(btStop); Напомним, что работа с кнопками и другими органами управления в приложениях Java была нами описана в 30 томе “Библиотеки системного программиста”, который называется “Microsoft Visual J++. Создание приложений на языке Java. Часть 1”. Последнее, что делает метод init перед тем как возвратить управление, это получение ссылки на интерфейс AudioClip: auClip = Applet.getAudioClip(getCodeBase(),m_ClipName); Адрес URL каталога, в котором расположен аплет, определяется с помощью метода getCodeBase, о котором мы говорили в предыдущей главе. Метод action Метод action получает управление, когда пользователь нажимает на одну из кнопок, расположенных в окне аплета. В зависимости от того, какая именно кнопка была нажата, выполняются различные действия. Если пользователь нажал кнопку Play, вызывается метод play для запуска однократного проигрывания звукового файла: auClip.play(); btStop.enable(); Сразу после того как проигрывание будет запущено, приложение разблокирует кнопку Stop, предоставляя пользователю возможность прервать звучание. В том случае, когда пользователь нажал кнопку Loop, вызывается метод loop, запусчкающий проигрывание звукового файла в цикле: auClip.loop(); fLoopPlay = true; btStop.enable(); После запуска устанавливается флаг fLoopPlay и разблокируется кнопка Stop. И, наконец, если пользователь нажимает кнопку Stop, выполняется остановка проигрывания методом stop: auClip.stop(); fLoopPlay = false; btStop.disable(); Флаг fLoopPlay сбрасывается, после чего кнопка Stop блокируется. Метод start Метод start получает управление при первом запуска аплета, а также когда страница документа появляется вновь после того как пользователь временно переходил к просмотру другой страницы. Наша реализация метода start возобновляет циклическое проигрывание, если оно выполнялось, когда пользователь покинул страницу с аплетом: if(fLoopPlay) auClip.loop(); Метод stop Если пользователь запустил проигрывание звукового файла в цикле, а затем перешел к просмотру другой страницы, метод stop останавливает циклическое проигрывание: if(fLoopPlay) auClip.stop(); Когда пользователь вернется к просмотру нашей страницы, метод start, описанный выше, возобновит проигрывание звукового файла. 6 ВЗАИМОДЕЙСТВИЕ МЕЖДУ АПЛЕТАМИ До сих пор наши аплеты жили в документах HTML сами по себе. В этой главе мы расскажем о том, как организовать взаимодействие между аплетами, расположенными в одном и том же документе HTML. Как аплеты могут взаимодействовать друг с другом? С помощью интерфейса AppletContext, о котором вы скоро узнаете, аплеты могут получать списки всех аплетов для текущего документа HTML. Эти списки состоят из ссылок на соответствующие объекты класса Applet, пользуясь которыми можно получить доступ к полям и методам, определенным в этих аплетах как public, могут получать строку описания аплета, описание параметров и значения, передаваемые аплетам через параметры в документе HTML. Таким образом, аплеты могут взаимодействовать друг с другом достаточно тесным образом. Использование интерфейса AppletContext Для того чтобы получить список всех аплетов, расположенных в текущем документе HTML, ваш аплет прежде всего должен получить ссылку на интерфейс AppletContext. Затем нужно вызвать метод getApplets, возвращающий искомый список. Рассмотрим этот процесс подробно. Получение контекста аплетов Для получения контекста аплетов, или ссылки на интерфейс AppletContext вы должны воспользоваться методом getAppletContext, определенным в классе Applet: AppletContext appContext; appContext = getAppletContext(); Далее, вызывая методы, определенные в интерфейсе AppletContext, вы можете получить ссылку на конкретный аплет или список ссылок на все аплеты. Получение ссылки на аплет Метод getApplet возвращает ссылку на аплет, заданный своим именем: public abstract Applet getApplet(String name); Это имя должно быть указано в параметре NAME оператора языка HTML, или оно должно передаваться аплету через параметр с именем NAME. Первый из этих способов демонстрируется ниже: Здесь параметр NAME задает для аплета MyApplet имя TestApplet. Второй способ был использован нами в следующем фрагменте документа HTML: Если аплет с указанным именем имеется в текущем документе HTML, метод getApplet возвращает ссылку на соответствующий объект. Пользуясь этой ссылкой, можно получить доступ к полям и методам, определенным в аплете как public. Если же аплет не найден, метод getApplet возвращает значение null. Получение списка всех аплетов С помощью метода getApplet вы сможете получить ссылку на один аплет, заданный своим именем, но наша задача - получить ссылки на все аплеты, расположенные в текущем документе HTML. Это можно сделать с помощью метода getApplets, определенного следующим образом: public abstract Enumeration getApplets(); Перед вызовом этого метода вы должны определить список класса Enumeration, например, следующим образом: Enumeration eApplets; eApplets = appContext.getApplets(); Просмотр списка аплетов Список класса Enumeration можно просмотреть в цикле только один раз, вызывая для получения очередного элемента списка метод nextElement: while(eApplets.hasMoreElements()) { Applet currentApplet = (Applet)(eApplets.nextElement()); . . . } Для проверки условия завершения цикла следует вызывать метод hasMoreElements, который возвращает значение true, если в процессе просмотра список еще не был опустошен. Заметим, что в полученном списке будет ссылка и на тот аплет, который выполняет поиск остальных аплетов. При необходимости в процессе просмотра вы можете выделить “свой” аплет следующим образом: if(currentApplet == this) { // Обработка “своего” аплета } Получение строки информации об аплете Обратите внимание, что в каждом нашем аплете мы определяли метод getAppletInfo: public String getAppletInfo() { return "Name: Inspector\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } Этот метод возвращает строку информации об аплете и, вообще говоря, необязателен. Однако если ваш аплет будет взаимодействовать с другими аплетами, то он может оказать существенную помощь в распознавании аплета. В самом деле, просматривая в цикле список аплетов, расположенных в текущем документе HTML, аплет-инспектор может вызывать для каждого найденного аплета метод getAppletInfo с целью получения строки, идентифицирующей данный аплет: appName = currentApplet.getAppletInfo(); Если же аплет не обеспечил метод getAppletInfo, то будет вызван одноименный метод из базового класса Applet, который просто возвращает значение null. Очевидно, это значение нельзя использовать для идентификации аплета. Получение информации о параметрах аплета Если аплет получает из документа HTML параметры, желательно (но вовсе не обязательно) определить метод getParameterInfo. Этот метод возвращает массив строк описаний параметров, в котором находятся имена и типы параметров, а также строки описаний параметров. Для примера мы привели фрагмент исходного текста приложения TEXTOUT, описанного нами в 30 томе, посвященном разработке приложений Java: public String[][] getParameterInfo() { String[][] info = { { PARAM_Str1, "String", "Text string to write" }, { PARAM_Str2, "String", "Text string to write" }, { PARAM_Str3, "String", "Text string to write" }, . . . { PARAM_Font1, "String", "Text font" }, { PARAM_Font2, "String", "Text font" }, { PARAM_Font3, "String", "Text font" }, . . . { PARAM_Type1, "String", "Font type" }, { PARAM_Type2, "String", "Font type" }, { PARAM_Type3, "String", "Font type" }, }; return info; } Вызывая метод getParameterInfo для найденного аплета, аплет-инспектор может многое узнать о его параметрах. Эти знания нужны, в частности, для получения значений параметров. Получение значений параметров аплета Для получения значения заданного параметра найденного аплета вы можете воспользоваться методом getParameter: String sParameter = currentApplet. GetParameter(“name”); Здесь мы получаем значение параметра с именем NAME. Обращение к полям и методам других аплетов Теперь вы научились искать аплеты, расположенные в текущем документе HTML, получая список ссылок на соответствующие объекты. Однако для того чтобы получить доступ к полям и методам найденных аплетов, вы должны сделать еще одну вещь. Нужно импортировать в аплет, который занимается поиском, описание класса аплета, к полям и методам которого будет выполняться обращение. Поясним это. Все аплеты, как вы знаете, происходят от класса Applet. Они добавляют в этот класс свои поля и методы, а также переопределяют методы из базового класса. В процессе поиска аплетов метод nextElement возвращает ссылку на объект, принадлежащий к классу Object, который мы можем преобразовать к классу Applet: Applet currentApplet = (Applet)(eApplets.nextElement()); Однако такое преобразование сможет открыть нам доступ только к тем полям и методам, которые определены в базовом классе. Для того чтобы получить доступ к полям и методам класса найденного аплета, мы должны преобразовать значение, полученное от метода nextElement, к ссылке на класс этого аплета. Как это можно сделать? Рассмотрим конкретный пример, использованный нами в приложении Inspector, полные исходные тексты которого вы найдете ниже. Это приложение управляет работой аплета Audio, описанного в предыдущей главе и предназначенного для проигрывания звукового файла. В классе Audio определено поле auClip, в котором хранится ссылка на интерфейс AudioClip: public class Audio extends Applet { private String m_ClipName = "kaas.au"; private final String PARAM_ClipName = "ClipName"; AudioClip auClip; . . . } Аплет Inspector получает доступ к полю auClip и вызывает методы, предназначенные для управления проигрыванием звукового файла. Таким образом, аплет Inspector пользуется полем auClip, определенным в другом аплете. Чтобы это стало возможным, в исходном тексте аплета Inspector импортируется класс Audio, как это показано ниже: import java.applet.*; import java.awt.*; import java.util.*; import Audio; Когда в процессе поиска аплетов аплет Inspector обнаруживает аплет Audio, он сохраняет ссылку на этот аплет в поле appAudio, выполняя явное преобразование типов: Audio appAudio = null; . . . if(appName.equals("Name: Audio")) { appAudio = (Audio)currentApplet; } Теперь, пользуясь значением из поля appAudio, можно обращаться к полю auClip, определенному в аплете Audio: appAudio.auClip.play(); Приложение Inspector Аплет Inspector располагается в одном документе HTML с приложениями Audio и Rectangles, которые уже были описаны в нашей книге (рис. 6.1). Рис. 6.1. Документ HTML, в котором расположены три аплета - Inspector, Audio и Rectangles В верхней части окна аплета Inspector расположены кнопки, дублирующие одноименные кнопки аплета Audio. С их помощью можно заставить аплет Audio проигрывать файл в однократном режиме или в цикле, а также остановить проигрывание. В нижней части аплета Inspector отображается список имен аплетов, найденных в текущем документе HTML. Для списка используются первые строки описаний аплетов, полученные методом getAppletInfo. Исходные тексты приложения Основной файл исходных текстов вы найдете в листинге 6.1. Листинг 6.1. Файл Inspector\Inspector.java // ========================================================= // Аплет, который получает список всех аплетов // и управляет аплетом Audio // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; import java.util.*; // Импортируем класс Audio, так как нам нужен доступ к // его полям import Audio; public class Inspector extends Applet { // Контекст аплетов AppletContext appContext; // Список аплетов, расположенных в документе HTML Enumeration eApplets; // Ссылка на аплет Audio Audio appAudio = null; // Кнопка для однократного проигрывания Button btPlay; // Кнопка для проигрывания в цикле Button btLoop; // Кнопка для остановки проигрывания Button btStop; // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Inspector\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Создаем кнопку для однократного проигрывания btPlay = new Button("Play"); // Создаем кнопку для проигрывания в цикле btLoop = new Button("Loop"); // Создаем кнопку для остановки проигрывания btStop = new Button("Stop"); // Блокируем эту кнопку, так как пока еще // проигрывание не запущено btStop.disable(); // Добавляем кнопки в окно аплета add(btPlay); add(btLoop); add(btStop); } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Получаем контекст аплетов appContext = getAppletContext(); // Получаем список всех аплетов в документе HTML eApplets = appContext.getApplets(); // Сбрасываем счетчик цикла int i = 0; // Цикл по аплетам while(eApplets.hasMoreElements()) { // Имя текущего аплета String appName; // Получаем ссылку на очередной аплет Applet currentApplet = (Applet)(eApplets.nextElement()); // Получаем строку информации об аплете appName = currentApplet.getAppletInfo(); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(appName, "\r\n"); appName = new String((String)st.nextElement()); // Отображаем имя найденного аплета g.drawString(appName , 10, 15 * i + 50); // Если нашли аплет Audio, запоминаем ссылку // на него в поле appAudio if(appName.equals("Name: Audio")) { appAudio = (Audio)currentApplet; } // Увеличиваем счетчик аплетов i++; } } // ------------------------------------------------------- // action // Метод вызывается, когда пользователь выполняет // действие над компонентами // ------------------------------------------------------- public boolean action(Event evt, Object obj) { // Ссылка на кнопку, от которой пришло сообщение Button btn; // Проверяем, что событие вызвано кнопкой, а не // другим компонентом if(evt.target instanceof Button) { // Получам ссылку на кнопку, вызвавшую событие btn = (Button)evt.target; // Выполняем ветвление по кнопкам // Нажата кнопка однократного проигрывания if(evt.target.equals(btPlay)) { // Запускаем однократное проигрывание appAudio.auClip.play(); // Разблокируем кнопку остановки проигрывания btStop.enable(); } // Нажата кнопка проигрывания в цикле else if(evt.target.equals(btLoop)) { // Запускаем проигрывание в цикле appAudio.auClip.loop(); // Разблокируем кнопку остановки проигрывания btStop.enable(); } // Нажата кнопка остановки проигрывания else if(evt.target.equals(btStop)) { // Останавливаем проигрывание appAudio.auClip.stop(); // Блокируем кнопку остановки проигрывания btStop.disable(); } // Если событие возникло от неизвестной кнопки, // мы его не обрабатываем else { return false; } // Возвращаем признак того, что мы обработали событие return true; } // Если событие вызвано не кнопкой, // мы его не обрабатываем return false; } } Файл документа HTML, содержащий все три аплета, приведен в листинге 6.2. Листинг 6.2. Файл Inspector\Inspector.html Inspector



The source. Описание исходных текстов Обращаем еще раз ваше внимание на то, что в исходном тексте аплета Inspector импортируется описание класса Audio: import Audio; Это необходимо для того чтобы аплет мог получить доступ к полю auClip, определенному в этом аплете. Рассмотрим поля и самые важные методы класса Inspector. Поля класса Inspector В поле appContext хранится ссылка на интерфейс AppletContext, с помощью которого мы будем получать список аплетов. Этот список будет записан в поле eApplets класса Enumeration. Когда в процессе поиска наш аплет найдет аплет Audio, то в поле appAudio будет записана ссылка на него. Кроме того, в классе Inspector определены поля btPlay, btLoop и btStop для хранения ссылок на кнопки управления аплетом Audio. Метод getAppletInfo Метод getAppletInfo предоставляет другим заинтересованным аплетам (и себе, в частности) строку информации об аплете Inspector. Эта строка аналогична строкам, которые возвращают другие наши аплеты. Метод init Метод init создает три кнопки, предназначенные для управления аплетом Audio, причем кнопка с названием Stop блокируется: btPlay = new Button("Play"); btLoop = new Button("Loop"); btStop = new Button("Stop"); btStop.disable(); Далее созданные кнопки добавляются в окно аплета Inspector. add(btPlay); add(btLoop); add(btStop); Метод paint После раскрашивания фона окна и рисования рамки метод paint получает с помощью метода getAppletContext ссылку на интерфейс AppletContext: appContext = getAppletContext(); Далее с помощью этой ссылки и метода getApplets приложение получает список всех аплетов, расположенных в текущем документе HTML; eApplets = appContext.getApplets(); Вслед за этим метод paint запускает цикл, в котором он получает ссылки на все найденные аплеты: while(eApplets.hasMoreElements()) { . . . } В этом цикле с помощью метода nextElement приложение получает ссылку на очередной аплет и, после преобразования ее к типу Applet, сохраняет в переменной currentApplet: Applet currentApplet = (Applet)(eApplets.nextElement()); Для каждого найденного аплета вызывается метод getAppletInfo: appName = currentApplet.getAppletInfo(); Полученная строка обрезается до первого символа возврата каретки или перевода на новую строку и записывается в переменную appName: StringTokenizer st; st = new StringTokenizer(appName, "\r\n"); appName = new String((String)st.nextElement()); Содержимое этой переменной (имя аплета) отображается в окне аплета Inspector со сдвигом по вертикали, который завивит от номера найденного аплета: g.drawString(appName , 10, 15 * i + 50); В том случае, если в процессе получения строк информации об аплете был найден аплет Audio, выполняется преобразование типа ссылки на этот аплет и сохранение этой ссылки в поле appAudio: if(appName.equals("Name: Audio")) { appAudio = (Audio)currentApplet; } Метод action Метод action обрабатывает события, вызванные нажатием кнопок в окне аплета Inspector. Обработка заключается в вызове сооветствующего метода с использованием ссылки на аплет Audio. Например, если пользователь нажал кнопку Play, метод action вызывает метод play: if(evt.target.equals(btPlay)) { appAudio.auClip.play(); btStop.enable(); } Обратите внимание, что здесь мы ссылаемся через поле appAudio на поле auClip, определенное в аплете Audio. 7 КОМБИНИРОВАННЫЕ ПРИЛОЖЕНИЯ JAVA Наши предыдущие приложения были либо аплетами, либо автономными приложениями с консольным окном. Основной класс аплетов был унаследован от класса Applet, а в классе консольных приложений был определен метод main. В этой главе мы расскажем о том, как создавать комбинированные приложения, двоичный файл .class которых способен работать и как аплет, встроенный в документ HTML, и как автономное приложение. Система автоматизированного проектирования приложений Java Applet Wizard, встроенная в Microsoft Visual J++, позволяет автоматически создавать исходные тексты шаблонов таких комбинированных приложений. Структура комбинированных приложений Рассмотрим структуру комбинированного приложения Combi, полные исходные тексты которого мы привели ниже в этой главе. Главный класс комбинированного приложения Класс Combi создан на базе класса Applet, что необходимо для обеспечения работы этого приложения под управлением навигатора Internet: public class Combi extends Applet { . . . public static void main(String args[]) { . . . } public String getAppletInfo() { . . . } public void init() { . . . } public void paint(Graphics g) { . . . } } Обратите внимание, что наряду с методами, которые обычно определяются аплетами, такими как getAppletInfo, init и paint, в классе комбинированного приложения определен метод main. Если приложение запускается как аплет, метод main не получает управления. Если же приложение запущено автономно, этот метод первым получает управление и выполняет все необходимые инициализирующие действия. Главное из этих действий - создание окна на базе класса Frame для размещения в нем аплета, создание аплета и вызов функций инициализации аплета. Ниже мы привели исходный текст метода main приложения Combi, созданный для нас системой Java Applet Wizard: public static void main(String args[]) { CombiFrame frame = new CombiFrame("Combi"); frame.show(); frame.hide(); frame.resize( frame.insets().left + frame.insets().right + 320, frame.insets().top + frame.insets().bottom + 240); Combi applet_Combi = new Combi(); frame.add("Center", applet_Combi); . . . applet_Combi.init(); applet_Combi.start(); frame.show(); } Прежде всего, метод main создает объект frame класса CombiFrame, определенного в нашем приложении на базе класса Frame (окно фрейма). Напомним, что класс Frame, который был нами описан в 30 томе “Библиотеки системного программиста”, позволяет приложениям Java создавать окна, напоминающие окна обычных приложений Windows. Метод show отображает окно фрейма. Далее в методе main выполняется изменение размеров окна фрейма, перед чем окно скрывается методом hide. Для изменения размеров окна применяется метод resize, которому через первый и второй параметры передаются новые значения, соответственно, ширины и высоты окна. Размеры окна устанавливаются с учетом размеров внешней рамки и заголовка окна, для чего применяется метод insets. Поля left и right объекта класса Insets, ссылку на который возвращает метод insets, содержат ширину, соответственно, левой и правой части рамки окна. Поле top содержит высоту верхней части рамки окна с учетом заголовка, а поле bottom - высоту нижней части рамки окна. Далее метод main делает нечто интересное - он создает аплет класса Combi: Combi applet_Combi = new Combi(); Когда приложение Java встроено в документ HTML как аплет, объект аплета создается автоматически навигатором при просмотре этого документа и добавляется в окно навигатора. В случае автономного приложения мы сами должны создать аплет и добавить его в окно фрейма. Добавление аплета в окно фрейма выполняется методом add: frame.add("Center", applet_Combi); В 30 томе “Библиотеки системного программиста” мы использовали этот метод для добавления компонент в окно контейнера, которым, в частности, является окно фрейма. Создав аплет, мы должны вызвать методы init и start, как это происходит при инициализации аплета, встроенного в документ HTML: applet_Combi.init(); applet_Combi.start(); Перед завершением своей работы метод main отображает окно фрейма, внутри которого теперь находится окна аплета: frame.show(); Класс фрейма для комбинированного приложения Теперь о классе CombiFrame. Определение этого класса выглядит достаточно просто: class CombiFrame extends Frame { public CombiFrame(String str) { super (str); } public boolean handleEvent(Event evt) { switch (evt.id) { case Event.WINDOW_DESTROY: { dispose(); System.exit(0); return true; } default: return super.handleEvent(evt); } } } Класс CombiFrame создан на базе класса Frame и предназначен для создания окна фрейма, в которое будет добавлен аплет. В этом классе определен конструктор и метод handleEvent. Конструктор выполняет простую задачу - создание окна фрейма. Для этого он вызывает конструктор базового класса Frame, передавая ему через параметр строку заголовка окна. Задача метода handleEvent - удаление окна фрейма, когда пользователь пытается его закрыть, сделав, например, щелчок мышью по правой кнопке в заголовке окна. В случае такой попытки методу handleEvent передается извещение с кодом Event.WINDOW_DESTROY. В процессе обработки этого извещения метод handleEvent удаляет окно фрейма, вызывая метод dispose, а затем завершает работу приложения, вызывая статический метод exit из класса System. Все прочие извещения передаются методу handleEvent, определенному в базовом классе. Приложение Combi Приложение Combi имеет описанную выше структуру и потому способно работать как автономно, так и под управлением навигатора Internet. После запуска приложение определяет и отображает в своем окне текущий режим работы, а также некоторую информацию о среде выполнения. На рис. 7.1 показан внешний вид окна этого приложения, когда оно работает автономно. Рис. 7.1. Окно автономно работающего приложения Combi Приложение определяет платформу, на которой оно работает, название и версию операционной системы, каталог, в котором находится двоичный модуль приложения, и каталог, в котором расположены классы Java. Кроме того, сразу после запуска автономное приложение Combi, если оно выполняется в среде операционной системы Windows 95 или Windows NT, запускает программу калькулятора calc.exe, входящую в комплект этой операционной системы. Когда приложение Combi работает как аплет, встроенный в документ HTML, его окно имеет вид, показанный на рис. 7.2. Рис. 7.2. Окно приложения Combi, встроенного в документ HTML Из-за ограничений, которые накладываются на аплеты, в данном случае нашему приложению доступна не вся информация о среде выполнения. Кроме того, будучи загруженным с сервера Web, приложение не сможет запустить программу калькулятора, так как эта возможность для аплетов заблокирована. Однако при работе аплета в среде Microsoft Visual J++ запуск приложений (а также другие действия) разрешается, поэтому на экране появится окно клаькулятора. Исходные тексты приложения Исходные текст основного класса приложения Combi представлен в листинге 7.1. Листинг 7.1. Файл Combi\Combi.java // ========================================================= // Приложение, способное работать как аплет, включенный // в документ HTML, а также как автономное приложение Java // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.applet.*; import java.awt.*; // Импортируем класс CombiFrame import CombiFrame; // ========================================================= // Combi // Основной класс приложения // ========================================================= public class Combi extends Applet { // Режим работы приложения. Если это поле содержит // значение true, приложение работает автономно, если // false - в составе документа HTML под управлением // навигатора boolean m_fStandAlone = false; // ------------------------------------------------------- // main // Метод main получает управление, когда приложение // работает автономно // ------------------------------------------------------- public static void main(String args[]) { // Создаем окно класса CombiFrame, унаследованного от // класса Frame (окно фрейма) CombiFrame frame = new CombiFrame("Combi"); // Отображаем окно фрейма frame.show(); // Выполняем изменение размеров окна // Скрываем окно фрейма frame.hide(); // Изменяем размеры окна фрейма frame.resize( frame.insets().left + frame.insets().right + 320, frame.insets().top + frame.insets().bottom + 240); // Создаем аплет класса Combi Combi applet_Combi = new Combi(); // Добавляем окно этого аплета в окно фрейма frame.add("Center", applet_Combi); // Устанавливаем признак работы в режиме // автономного прилождения Java applet_Combi.m_fStandAlone = true; // Вызываем методы init и start аплета класса Combi applet_Combi.init(); applet_Combi.start(); // Отображаем окно фрейма frame.show(); } // ------------------------------------------------------- // getAppletInfo // Метод, возвращающей строку информации об аплете // ------------------------------------------------------- public String getAppletInfo() { return "Name: Combi\r\n" + "Author: Alexandr Frolov\r\n" + "E-mail: [email protected]" + "WWW: http://www.glasnet.ru/~frolov" + "Created with Microsoft Visual J++ Version 1.0"; } // ------------------------------------------------------- // init // Вызывается во время инициализации аплета // ------------------------------------------------------- public void init() { // Устанавливаем размеры окна аплета resize(320, 240); // Определяем имя операционной системы String str = System.getProperty("os.name"); // Если это Windows, запускаем калькулятор if(str.indexOf("Windows") != -1) { // Получаем указатель на класс Runtime Runtime rt = Runtime.getRuntime(); // Выполняем попытку запуска клькулятора try { rt.exec("calc.exe"); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } // ------------------------------------------------------- // paint // Метод paint, выполняющий рисование в окне аплета // ------------------------------------------------------- public void paint(Graphics g) { // Определяем текущие размеры окна аплета Dimension dimAppWndDimension = size(); // Выбираем в контекст отображения желтый цвет g.setColor(Color.yellow); // Закрашиваем внутреннюю область окна аплета g.fillRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Выбираем в контекст отображения черный цвет g.setColor(Color.black); // Рисуем рамку вокруг окна аплета g.drawRect(0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); // Рабочая строка String str; // Проверяем, в каком режиме работает приложение // и рисуем соответствующее сообщение в его окне if(m_fStandAlone) { g.drawString("Приложение работает" + " автономно", 10, 20); // Отображаем системные свойства, доступные // аплетам и автономным приложениям str = "System: " + System.getProperty("os.arch") + ", OS: " + System.getProperty("os.name") + ", ver. " + System.getProperty("os.version"); g.drawString(str, 10, 50); str = "User dir: " + System.getProperty("user.dir") + ", User home: " + System.getProperty("user.home"); g.drawString(str, 10, 65); } // Приложение работает под управлением навигатора else { g.drawString("Приложение работает" + " в документе HTML", 10, 20); // Отображаем системные свойства, доступные // аплетам str = "System: " + System.getProperty("os.arch") + ", OS: " + System.getProperty("os.name") + ", ver. " + System.getProperty("os.version"); g.drawString(str, 10, 50); } } } В листинге 7.2 вы найдете исходные тексты класса CombiFrame, на базе которого создается окно фрейма, когда приложение запускается автономно. Листинг 7.2. Файл CombiFrame\CombiFrame.java // ========================================================= // Исходный текст класса CombiFrame для прилдожения Combi // // (C) Фролов А.В, 1997 // // E-mail: [email protected] // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.awt.*; // ========================================================= // CombiFrame // Класс для окна фрейма // ========================================================= class CombiFrame extends Frame { // ------------------------------------------------------- // Конструктор класса CombiFrame // ------------------------------------------------------- public CombiFrame(String str) { // Вызываем конструктор базового класса super (str); } // ------------------------------------------------------- // handleEvent // Метод handleEvent обрабатывает извещение WINDOW_DESTROY // ------------------------------------------------------- public boolean handleEvent(Event evt) { switch (evt.id) { // Когда пользователь закрывает окно фрейма, // метод handleEvent завершает работу автономного // приложения case Event.WINDOW_DESTROY: { // Удаление окна фрейма dispose(); // Завершение работы приложения System.exit(0); return true; } default: // Для обработки других извещений вызываем // метод handleEvent из базового класса return super.handleEvent(evt); } } } И, наконец, в листинге 7.3 представлен исходный текст документа HTML, в который наше приложение встроено как аплет. Листинг 7.3. Файл Combi\Combi.html Combi

The source. Описание исходных текстов Так как ранее мы уже рассказывали довально подробно о классах приложения Combi, остановимся только на основных моментах. Так как определение класса CombiFrame расположено в отдельном файле, мы должны импортировать описание этого класса в исходных текстах класса Combi: import CombiFrame; Поля класса Combi В классе Combi определено поле с именем m_fStandAlone: boolean m_fStandAlone = false; Если приложение работает автономно, в это поле записывается значение true, если как аплет в составе документа HTML - false. По умолчанию это поле инициализируется значением false, однако если приложение запускается автономно, метод main записывает в него значение true: applet_Combi.m_fStandAlone = true; Метод init Метод init вызывается независимо от режима, в котором работает приложение - автономно или как аплет. После изменения размеров окна аплета методом resize метод init определяет некоторые параметры среды выполнения приложения. Для этого используется метод getProperty, определенный в классе System. Подробное описание этого метода выходит за рамки нашей книги. Однако мы отметим, что передавая этому методу в виде текстовой строки названия параметров среды выполнения, можно получить в текстовом виде значения этих параметров. Наш метод init использует метод getProperty для определения названия операционной системы, под управлением которой работает приложение, передавая ему строку "os.name": String str = System.getProperty("os.name"); Далее метод init проверяет, есть ли в строке названия операционной системы слово Windows, и если есть, пытается запустить программу калькулятора: if(str.indexOf("Windows") != -1) { Runtime rt = Runtime.getRuntime(); try { rt.exec("calc.exe"); } catch(Exception ioe) { System.out.println(ioe.toString()); } } Калькулятор запускается с помощью класса Runtime, который предоставляет системно-зависимые методы (именно поэтому перед его использованием мы проверили название операционной системы). Ссылка на объект класса Runtime должна быть получена с помощью статического метода getRuntime. Получив такую ссылку, мы запускаем программу калькулятора с помощью метода exec, который определен в реализации класса Runtime для операционных систем Microsoft Windows 95 и Microsoft Windows NT. Заметим, что одно из важнейших достоинств приложений Java заключается в возможности достижения независимости от платформ, на которых они выполняются. Поэтому использование класса Runtime оправдано только тех случаях, когда приложение Java разрабатывается специально для конкретной платформы. Метод paint После раскрашивания окна аплета метод paint анализирует содержимое поля m_fStandAlone, определяя режим работы приложения. Если приложение работает автономно, метод отображает в окне приложения соответствующее сообщение: g.drawString("Приложение работает автономно", 10, 20); Затем метод paint определяет и отображает такие параметры среды выполнения приложения, как название архитектуры компьютера, название и версия операционной системы: str = "System: " + System.getProperty("os.arch") + ", OS: " + System.getProperty("os.name") + ", ver. " + System.getProperty("os.version"); g.drawString(str, 10, 50); Строкой ниже отображается путь к каталогу, в котором находится двоичный модуль приложения и путь к каталогу, где находится библиотка классов Java: str = "User dir: " + System.getProperty("user.dir") + ", User home: " + System.getProperty("user.home"); g.drawString(str, 10, 65); В том случае, когда приложение запущено как аплет, последние два параметра не определяются, так как они недоступны для аплетов из соображений безопасности. ЛИТЕРАТУРА 1. Фролов А.В., Фролов Г.В. Библиотека системного программиста. М.: ДИАЛОГ-МИФИ Т.11 - 13. Операционная система Microsoft Windows 3.1 для программиста, 1994 Т.14. Графический интерфейс GDI в Microsoft Windows, 1994 Т.15. Мультимедиа для Windows, 1994 Т.22. Операционная система Windows 95 для программиста, 1996 Т.23. Глобальные сети компьютеров. Практическое введение в Internet, E-Mail, FTP, WWW и HTML, программирование для Windows Sockets Т.29. Сервер Web своими руками. Язык HTML, приложения CGI и ISAPI, установка серверов Web для Windows, 1997 Т.30. Microsoft Visual J++. Создание приложений на языке Java. Часть 1, 1997 2. Д. Родли, Создание Java-апплетов: Пер. с англ., К: НИПФ “ДиаСофт Лтд.”, 1996 3. S. Davic, Learn Lava Now, Microsoft Press, One Microsoft Way, 1996 4. К. Джамса, Java: Пер. с англ., Мн.: ООО “Поппури”, 1996 5. Баженова И.Ю.,Язык программирования Java, М.: ДИАЛОГ-МИФИ, 1997 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ ABORT, 115; 116 accept, 88 activeCount, 8 add, 142 addImage, 114 ALLBITS, 115; 116 AppletContext, 133 AU, 128 AudioClip, 128 AVI, 121 BMP, 110 BufferedInputStream, 43 BufferedOutputStream, 44; 48 ByteArrayInputStream, 43; 53 ByteArrayOutputStream, 44; 52 canRead, 63 canWrite, 63 CGI, 76; 80; 101; 148 checkAccess, 8 checkAll, 114 checkID, 114 close, 50; 70; 89; 90; 96 commentChar, 56 connect, 102 countStackFrames, 8 countTokens, 61 currentThread, 8 DatagramPacket, 95; 96 DatagramSocket, 95 DataInputStream, 43 DataOutputStream, 44; 47 delete, 64 destroy, 8 dispose, 142 drawImage, 110; 111 dumpStack, 8 enumerate, 8 Enumeration, 133 eolIsSignificant, 56 equals, 64; 77; 81 ERROR, 115; 116 Event.WINDOW_DESTROY, 142 exists, 63 exit, 142 File, 42; 63 FileDescriptor, 42 FileInputStream, 43; 49 FileNotFoundException, 48 FileOutputStream, 44; 48 FilterInputStream, 42 FilterOutputStream, 44 flush, 50; 111 Frame, 141; 142 FRAMEBITS, 115; 116 getAbsolutePath, 63 getAddress, 77; 96 getAllByName, 77 getApplet, 133 getAppletContext, 133 getAppletInfo, 134; 141 getApplets, 133 getAudioClip, 128 getByName, 77 getCodeBase, 110 getConten, 81 getContent, 102 getContentEncoding, 102 getContentLength, 102 getContentType, 102 getData, 96 getDate, 102 getDefaultUseCaches, 102 getDocumentBase, 110 getDoInput, 102 getDoOutput, 102 getErrorsAny, 115 getErrorsID, 115 getExpiration, 103 getFD, 70 getFile, 81 getFilePointer, 70 getGraphics, 111 getHeight, 111 getHost, 81 getHostName, 77 getIfModifiedSince, 102 getImage, 110 getInetAddress, 89; 90 getInputStream, 90; 101; 102 getLastModified, 102 getLength, 96 getLocalHost, 77 getLocalPort, 90; 95 getName, 8; 63 getOutputStream, 90; 101; 102 getParameter, 134 getParameterInfo, 134 getParent, 64 getPath, 63 getPort, 81; 90; 96 getPriority, 8 getProtocol, 81 getRef, 81 getThreadGroup, 8 getUseCaches, 102 getWidth, 111 GIF, 76; 110 GoldWave, 128 Graphics, 110 handleEvent, 142 hashCode, 64; 81 hasMoreElements, 61; 134 hasMoreTokens, 61 HEIGHT, 115; 116 hide, 141 Image, 111 ImageObserver, 113; 115; 122 imageUpdate, 115; 116 InetAddress, 77 init, 141; 142 InputStream, 41; 89 insets, 141 interrupt, 8 interrupted, 8 IOException, 48; 49 isAbsolute, 63 isAlive, 8 ISAPI, 4; 76; 80; 101; 148 isDaemon, 8; 40 isDirectory, 63 isErrorAny, 115 isErrorID, 115 isFile, 63 isInterrupted, 8 Java Applet Wizard, 141 java.lang.Thread, 7 join, 8; 35 JPEG, 110; 122 lastModified, 64 length, 64; 70 lineno, 57 LineNumberInputStream, 43 list, 64 loop, 128 lowerCaseMode, 56 MAX_PRIORITY, 7 MediaTracker, 113; 114; 122 MediaTracker.ABORTED, 115 MediaTracker.COMPLETE, 115 MediaTracker.LOADING, 115 MediatTracker.ERRORED, 115 Microsoft Information Server, 80 MIN_PRIORITY, 7 mkdir, 64 mkdirs, 64 nextElement, 61; 133 nextToken, 57; 61 NORM_PRIORITY, 7 NoSuchElementException, 61 notify, 35 notifyAll, 35 Object, 41 openConnection, 81 openStream, 80 ordinaryChar, 56 ordinaryChars, 56 OutputStream, 41; 43; 48; 89 paint, 141 parseNumbers, 56 PipedInputStream, 43; 44 PipedOutputStream, 43; 44 play, 128 print, 45 println, 45 PrintStream, 44 process, 6 PROPERTIES, 115; 116 pushBack, 57 PushBackInputStream, 43 quoteChar, 56 RandomAccesFile, 41 RandomAccessFile, 70 read, 45; 49; 71; 89 readBoolean, 49; 71 readByte, 49; 71 readChar, 49; 71 readDouble, 49; 71 readFloat, 49; 71 readFully, 49; 71 readInt, 49; 71 readLine, 49; 71 readLong, 49; 71 readShort, 49; 71 readUnsignedByte, 49; 71 readUnsignedShort, 50; 71 readUTF, 50; 71 receive, 96 renameTo, 64 resetSyntax, 56 resize, 141 resume, 8; 34 run, 8 Runnable, 7; 9 sameFile, 81 SecurityException, 48 seek, 70 send, 96 SequenceInputStream, 43 ServerSocket, 88 setDaemon, 8; 40 setDefaultUseCaches, 102 setDoInput, 102 setDoOutput, 102 setIfModifiedSince, 102 setName, 8 setPriority, 8 setUseCaches, 102 show, 141; 142 skipBytes, 50; 70 slashSlashComments, 56 slashStarComments, 56 sleep, 8; 34 Socket, 89 sockets, 87 SOMEBITS, 115; 116 start, 8; 142 statusAll, 114 statusID, 114 stop, 8; 128 StreamTokenizer, 42; 56 StringBufferInputStream, 43; 53 StringTokenizer, 61 Sun, 128 suspend, 9; 34 synchronized, 34 System.err, 41 System.in, 41 System.out, 41 task, 6 TCP/IP, 76; 79 thread, 6; 7 toExternalForm, 81 toString, 9; 64; 77; 81; 90 TT_EOF, 57 TT_EOL, 57 TT_NUMBER, 57 TT_WORD, 57 Universal Resource Locator, 79 UNIX, 80; 87 UnknownHostException, 77 URL, 79; 80; 101 URLConnection, 81; 101; 102 wait, 35 waitForAll, 114 waitForID, 114 whitespaceChars, 56 WIDTH, 115; 116 Windows Sockets, 79; 88 WM_TIMER, 10 wordChars, 56 write, 49; 71; 89 writeBoolean, 49; 71 writeByte, 49; 71 writeBytes, 49; 71 writeChar, 49; 71 writeChars, 49; 71 writeDouble, 49; 71 writeFloat, 49; 71 writeInt, 49; 71 writeLong, 49; 71 writeShort, 49; 71 writeUTF, 49; 71 yield, 9 адрес IP, 76; 79 адрес localhost, 89 адресация в сетях TCP/IP, 96 блокировка задачи, 34 буферизованный выходной поток, 48 возобновление работы задачи, 34 датаграммные сокеты, 88; 95 доменный адрес, 76; 79 задача, 6 задача-демон, 40 звуковые клипы, 128 интерфейс AudioClip, 128 многосекционный файл GIF, 121 номер порта, 79 ожидание завершения работы задачи, 35 ожидание извещения, 35 планировщик, 6 порт, 80; 87 потоковые сокеты, 88 приоритеты задач, 7 процесс, 6 расширения сервера Web, 101 синхронизация задач, 33 синхронизированный метод, 34 сокеты, 87 стандартный поток ввода, 44 стандартный поток вывода, 44 стандартный поток вывода сообщений об ошибках, 44 стандратные потоки, 41 типы потоков, 41 универсальный адрес ресуросв URL, 79 ОГЛАВЛЕНИЕ АННОТАЦИЯ ВВЕДЕНИЕ БЛАГОДАРНОСТИ КАК СВЯЗАТЬСЯ С АВТОРАМИ 1 МУЛЬТИЗАДАЧНОСТЬ Процессы, задачи и приоритеты Процесс Задача Приоритеты задач в приложениях Java Реализация мультизадачности в Java Методы класса Thread Создание дочернего класса на базе класса Thread Реализация интерфейса Runnable Применение мультизадачности для анимации Приложение MultiTask Исходные тексты приложения Описание исходных текстов Конструктор MultiTask Метод getAppletInfo Метод init Метод destroy Метод paint Метод start Метод stop Метод run Приложение Rectangles Исходные тексты приложения Описание исходных текстов Метод paint Метод start Метод stop Метод run Метод mouseEnter Метод mouseExit Приложение MultiTask2 Исходные тексты приложения Описание исходного текста Поля класса MultiTask2 Метод paint класса MultiTask2 Метод start класса MultiTask2 Метод stop класса MultiTask2 Поля класса DrawRectangles Конструктор класса DrawRectangles Метод run класса DrawRectangles Класс DrawEllipse Приложение Scroller Исходные тексты приложения Описание исходных текстов Поля класса Scroller Метод init Метод paint Метод start Метод stop Метод run Приложение HorzScroll Исходные тексты приложения Описание исходных текстов Поля класса HorzScroll Метод init Метод paint Метод run Синхронизация задач Синхронизация методов Блокировка задачи Блокировка на заданный период времени Временная приостановка и возобновление работы Ожидание извещения Ожидание завершения задачи Приложение Synchro Исходные тексты приложения Описание исходных текстов Поля основного класса аплета Метод start основного класса Метод stop основного класса Поля класса DrawRectangles Конструктор класса DrawRectangles Метод run класса DrawRectangles Поля класса NotifyTask Метод run класса NotifyTask Задачи-демоны 2 РАБОТА С ФАЙЛАМИ Классы Java для работы с потоками Стандартные потоки Базовые классы для работы с файлами и потоками Класс InputStream Класс OutputStream Класс RandomAccesFile Класс File Класс FileDescriptor Класс StreamTokenizer Производные от класса InputStream Класс FilterInputStream Класс BufferedInputStream Класс DataInputStream Класс LineNumberInputStream Класс PushBackInputStream Класс ByteArrayInputStream Класс StringBufferInputStream Класс FileInputStream Класс PipedInputStream Класс SequenceInputStream Производные от класса OutputStream Класс FilterOutputStream Класс BufferedOutputStream Класс DataOutputStream Класс PrintStream Класс ByteArrayOutputStream Класс FileOutputStream Класс PipedOutputStream Работа со стандартными потоками Стандартный поток ввода Стандартный поток вывода Стандртный поток вывода сообщений об ошибках Приложение Standard Исходный текст приложения Описание исходного текста Создание потоков, связанных с файлами Создание потока для форматированного обмена данными Добавление буферизации Исключения при создании потоков Запись данных в поток и чтение данных из потока Простейшие методы Методы для чтения и записи форматированных данных Закрывание потоков Принудительный сброс буферов Приложение StreamDemo Исходный текст приложения Описание исходного текста приложения Потоки в оперативной памяти Класс ByteArrayOutputStream Класс ByteArrayInputStream Класс StringBufferInputStream Приложение MemStream Исходные тексты приложения Описание исходных текстов Метод init Метод paint Класс StreamTokenizer для разбора входных потоков Конструктор класса StreamTokenizer Методы класса StreamTokenizer Методы для настройки параметров разборщика Методы для разбора входного потока Приложение StreamToken Исходный текст приложения Описание исходного текста приложения Класс StringTokenizer Приложение StringToken Исходный текст приложения Описание исходного текста Работа с файлами и каталогами при помощи класса File Создание объекта класса File Определение атрибутов файлов и каталогов Проверка существования файла или каталога Проверка возможности чтения и записи Определение типа объекта - файл или каталог Получение имени файла или каталога Получение абсолютного пути к каталогу Определение типа указанного пути - абсолютный или относительный Определение пути к файлу или каталогу Определение родительского каталога Определение длины файла в байтах Определение времени последней модификации файла или каталога Получение текстового представления объекта Получение значения хэш-кода Удаление файлов и каталогов Создание каталогов Переименование файлов и каталогов Сравнение объектов класса File Получение списка содержимого каталога Приложение FileInfo Исходный текст приложения FileInfo Описание исходного текста Приложение DirList Исходный текст приложения Описание исходного текста Произвольный доступ к файлам Приложение DirectFileAccess Исходные тексты приложения Описание исходных текстов Метод main Класс SimpleDBMS Поля класса SimpleDBMS Конструктор класса SimpleDBMS Метод close Метод AddRecord Метод GetRecordByNumber 3 СОЗДАНИЕ СЕТЕВЫХ ПРИЛОЖЕНИЙ Адрес IP и класс InetAddress Создание объекта класса InetAddress для локального узла Создание объекта класса InetAddress для удаленного узла Определение адреса IP Определение имени узла Сравнение адресов IP Приложение InetAddressDemo Исходные тексты приложения InetAddressDemo Описание исходных текстов Универсальный адрес ресурсов URL Класс URL в библиотеке классов Java Конструкторы класса URL Методы класса URL Метод openStream Метод getContent Метод getHost Метод getFile Метод getPort Метод getProtocol Метод getRef Метод hashCode Метод sameFile Метод equals Метод toExternalForm Метод toString Метод openConnection Приложение URLDemo Исходный текст приложения Описание исходного текста Приложение ShowChart Исходные тексты приложения Описание исходного текста Поля класса ShowChart Метод init Метод paint Передача данных с использованием сокетов Работа с потоковыми сокетами Инициализация сервера Инициализация клиента Передача данных между клиентом и сервером Завершение работы сервера и клиента Конструкторы и методы класса Socket Конструкторы класса Socket Методы класса Socket Приложения SocketServ и SocketClient Исходный текст серверного приложения SocketServ Описание исходного текста серверного приложения SocketServ Исходный текст клиентского приложения SocketClient Описание исходного текста клиентского приложения SocketClient Использование датаграммных сокетов Класс DatagramSocket Класс DatagramPacket Приложения DatagramServer и DatagramClient Исходный текст приложения DatagramServer Описание исходного текста приложения DatagramServer Исходный текст приложения DatagramClient Описание исходного текста приложения DatagramClient Связь приложений Java с расширениями сервера Web Взаимодействие приложения Java и расширения сервера Web Класс URLConnection Приложение CallCGI Исходный текст приложения CallCGI Описание исходного текста приложения CallCGI Исходные тексты программы CGI 4 РАСТРОВЫЕ ИЗОБРАЖЕНИЯ И АНИМАЦИЯ Загрузка и рисование растрового изображения Класс Image Приложение ImageDraw Исходные тексты приложения Описание исходных текстов Поля класса ImageDraw Метод init Метод paint Ожидание загрузки изображений Применение класса MediaTracker Создание объекта класса MediaTracker Добавление изображений в объект класса MediaTracker Ожидание загрузки добавленных изображений Другие методы класса MediaTracker Применение интерфейса ImageObserver Приложение ImageDrawWait Исходные тексты приложения Описание исходных текстов Метод init Метод paint Приложение DrawImageObserver Исходные тексты приложения Описание исходных текстов Метод init Метод paint Метод imageUpdate Видео в окне аплета Приложение CDRotation Исходные тексты приложения Описание исходных текстов Метод start Метод stop Метод paint Метод run Метод displayImage 5 ЗВУК В АПЛЕТАХ JAVA Загрузка и проигрывание звуковых файлов Приложение Audio Исходные тексты приложения Описание исходного текста Поля класса Audio Метод getParameterInfo Метод init Метод action Метод start Метод stop 6 ВЗАИМОДЕЙСТВИЕ МЕЖДУ АПЛЕТАМИ Использование интерфейса AppletContext Получение контекста аплетов Получение ссылки на аплет Получение списка всех аплетов Просмотр списка аплетов Получение строки информации об аплете Получение информации о параметрах аплета Получение значений параметров аплета Обращение к полям и методам других аплетов Приложение Inspector Исходные тексты приложения Описание исходных текстов Поля класса Inspector Метод getAppletInfo Метод init Метод paint Метод action 7 КОМБИНИРОВАННЫЕ ПРИЛОЖЕНИЯ JAVA Структура комбинированных приложений Главный класс комбинированного приложения Класс фрейма для комбинированного приложения Приложение Combi Исходные тексты приложения Описание исходных текстов Поля класса Combi Метод init Метод paint ЛИТЕРАТУРА ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ ОГЛАВЛЕНИЕ 150 149 154 155
 
 [Весь Текст]
Страница: из 275
 <<-