
|
|
Главная \ Методичні вказівки \ ОСНОВИ ПРОГРАМУВАННЯ НА JAVA
ОСНОВИ ПРОГРАМУВАННЯ НА JAVA« Назад
ОСНОВИ ПРОГРАМУВАННЯ НА JAVA 22.01.2016 18:01
НАЦІОНАЛЬНА АКАДЕМІЯ УПРАВЛІННЯ ФАКУЛЬТЕТ КОМП'ЮТЕРНИХ НАУК КАФЕДРА ІНТЕЛЕКТУАЛЬНИХ СИСТЕМ ОСНОВИ ПРОГРАМУВАННЯ НА JAVA КИЇВ - 2013 У посібнику розглянуто основні методи програмування на мові Java. Матеріал, наведений в посібнику, стане в нагоді для студентів спеціальності “Інтелектуальні системи прийняття рішень” під час роботи над курсовим проектом з дисципліни “Об'єктно-орієнтоване програмування”. Здавалося б, на сьогоднішній день винайдені вже всі мови програмування, які тільки можна вигадати. Аж ні - з'явилася ще одна, з назвою Java. Ця мова стала помітно популярною за останні декілька років, через те, що вона орієнтована на найпопулярніше комп'ютерне навколишнє середовище - мережу Internet та сервери Web. Мова Java пішла від мови програмування Oak (а не від C++, як багато хто думає). Oak була пристосована для роботи в Internet та після цього перейменована на Java. Вивчаючи Java, ви будете приємно здивовані тим, що її синтаксис близький до синтаксису мови C++. Успадкувавши снайкраще від мови програмування C++, мова Java при цьому позбавилася деяких недоліків C++, внаслідок чого на ній стало простіше програмувати. У цій мові немає, наприклад, вказівників, що складні у використанні та потенційно можуть спричинити доступ програми до ділянки пам'яті, що не належить їй. Немає багаторазової спадковості та взірців, хоча функціональні можливості мови Java від цього не постраждали. Якщо ви вмієте програмувати на C++, для вас не складе особливих труднощів вивчити мову Java. Чому вам потрібно вивчати нову мову програмування Java? Якщо відповісти на це запитання стисло, то тому, що вона спеціально орієнтована на найпередовіші методи, спов'язані з мережею Internet. Популярність Internet, що росте з року в рік, та особливо серверів Web, створює для програмістів нові можливості для реалізації своїх можливостей. Величезна перевага Java в тому, що на цій мові можна створювати прикладну програму, здатну працювати на відмінних платформах. До мережі Internet підключені комп'ютери дуже різних типів - Pentium PC, Macintosh, робочі станції Sun та таке інше. Навіть у межах комп'ютерів, створених на базі процесорів Intel, існує декілька платформ, наприклад, Microsoft Windows версії 3.1, Windows 95, Windows NT, OS/2, Solaris, відмінні різноманітності управляючої програми операційної системи UNIX з графічною оболонкою XWindows. Між тим, створюючи сервер Web в мережі Internet, ви б напевно бажали, щоб його могла використовувати якомога більша кількість людей. В цьому випадку вам стануть в нагоді прикладні програми Java, призначені для роботи на відмінних платформах, та такі, що не залежать від конкретного типу процесора та операційної системи. Програми, складені на мові програмування Java, можна поділити за їх призначенням на дві великі категорії. До першої категорії мають відношення прикладні програми (додатки) Java, призначені для незалежної роботи під контролем спеціальної машини, що інтерпретує Java. Реалізації цієї машини створені для всіх основних комп'ютерних платформ. Друга категорія - це так звані аплети (applets). Аплети представляють собою різноманітність прикладної програми Java, що інтерпретуються віртуальною машиною Java, вбудованою практично до всіх сучасних браузерів. Прикладні програми, що можуть бути віднесені до першої категорії (ми будемо називати їх просто прикладними програмами Java), - це звичайні автономні програми. Оскільки вони не містять машинного коду та працюють під контролем спеціального інтерпретатора, їхня швидкодія помітно нижча, ніж у звичайних програм, складених, наприклад, на мові програмування C++. Однак не слід забувати, що програми Java без перетранслювання здатні працювати на будь-якій платформі, що саме по собі має велике значення в мережі Internet. Аплети Java вбудовуються у документи HTML, що запам'ятовуються на сервері Web. За допомогою аплетів ви можете зробити сторінки сервера Web динамічними та інтерактивними. Аплети дозволяють виконувати складну локальну обробку даних, отриманих від Web сервера або введених користувачем з клавіатури. З міркувань безпеки аплети (на відміну від звичайних прикладних програм Java) не мають жодного доступу до програми керування файлами локального комп'ютера. Всі дані для обробки вони можуть отримати тільки від Web сервера. Більш складну обробку даних можна виконувати, організувавши взаємодію між аплетами та розширеннями сервера Web - прикладною програмою CGI та ISAPI. Для підвищення виконання прикладної програми Java в сучасних вікнах перегляду використовується компілювання "негайно" - Just-In-Time compilation (JIT). При першому вертикальному прокручуванні аплета його код транслюється в звичайну виконану програму, що зберігається на дискові та запускається. Внаслідок загальна швидкість виконання аплета Java збільшується в декілька разів. Мова Java є об'єктно-орієнтованою та поставляється з достатньо об'ємною бібліотекою класів. Таким же чином, як і бібліотеки класів систем проектування прикладної програми на мові C++, бібліотеки класів Java значно спрощують розробку прикладної програми, надаючи у розпорядження програміста потужні засоби розв'язування розповсюджених завдань. Тому програміст може більше уваги приділити розв'язуванню прикладних програм, а не таких, як, наприклад, організація динамічних масивів, взаємодія з операційної системи або реалізація елементів інтерфейсу користувача. 1.1. Засоби проектувальникаСпочатку засоби розробки прикладної програми та аплетів Java були створені фірмою Sun Microsystems й досі вони користуються достатньою популярністю. В мережі Internet за адресою http://java.sun.com ви можете безкоштовно отримати комплект Java Development Kit (JDK). 1.1.1. Пакетні засобиДо JDK входять пакетні програми для компілювання текст-джерел прикладної програми Java, віртуальна машина, програма автоматизованого генерування документації по класах, довідник по класах Java та інші необхідні засоби. 1.1.2. Інтегровані засоби розробкиДля тих, хто звик користуватися візуальними засобами розробки, досяжні два інших інструменти, створених в Sun Microsystems. Це Java WorkShop 2.0 та Java Studio 1.0. Інтегрована система розробки прикладної програми Java WorkShop 2.0 містить традиційні та візуальні допоміжні програмні засоби, налагоджувальна програма, простора довідкова система по класах та мові Java. За допомогою Java WorkShop 2.0 ви можете створювати незалежні прикладні програми Java, аплети, компоненти JavaBeans та власні бібліотеки класів. Серед головних можливостей Java WorkShop 2.0 назвемо синтаксичне виділення у вікні редагування, можливість віддаленого налагодження прикладної програми Java, швидкодіючий компілятор, а також здатність працювати на відмінних платформах. Розробку елементів інтерфейсу користувача можна виконувати за допомогою зручного засобу візуального конструювання. Щодо Java Studio 1.0, то ця система дозволяє проектувати прикладну програму взагалі без кодування. Будівник збирає прикладну програму з готових елементів, встановлюючи між ними канали передавання запитів. Таким чином визначиться логіка роботи системи. Ця процедура ніж-то нагадує вузол побутової стереофонічної системи з окремих блоків, коли ви з'єднуєте провідниками входи та вихідні дані підсилювача, колонок, програвача компакт-дисків та інших пристроїв. В комплекті компонент JavaBeans, досяжних в Java Studio, є елементи, призначені для роботи з ресурсами Internet, базами даних та таблицями, формами та таким іншим. Конструювання інтерфейсу користувача в Java Studio, також як й у Java WorkShop, виконується візуально. 1.1.3. БраузериДля перевірки роботи аплетів вам необхідно встановити вікно перегляду, здатний працювати з аплетами Java. Така можливість є в усіх сучасних браузерах, тому ви можете вибрати будь-який з них. Краще всього перевіряти роботу створюваних вами аплетів у різних браузерах (спробуйте браузери Netscape Navigator та Internet Explorer). Це дозволить вам виявити й розв'язати задачі несумісності, перш ніж із ними зіткнеться користувач ваших програм. 1.1.4. Приєднання до InternetВідмітимо, що для запуску аплетів вам не потрібно обов'язково приєднуватися до Internet - ви можете вбудовувати аплети в документи HTML, розташовані на локальному диску вашого комп'ютера та переглядати ці документи вікном перегляду просто як локальні файли. Автономні прикладні програми Java працюють під контролем спеціального інтерпретатора (віртуальної машини Java), тому для їхнього налагодження вам також не потрібна мережа Internet. Однак є одна важлива обставина - аплети, що взаємодіють з розширеннями сервера Web, повинні бути введені в дію саме з цього сервера. В противному випадку їхня робота буде заблокована з міркувань безпеки. Якщо ви збираєтеся перевіряти роботу прикладної програми та аплетів Java, що взаємодіють з сервером Web, ви можете скористуватися власним сервером в Internet або в корпоративній мережі Intranet (якщо вони у вас є). Можна також встановити сервер Web, що входить до комплекту операційної системи Windows NT Workstation версії 4.0, або Personal Web Service для керівної програми операційної системи Windows 95. 1.1.5. Що ще треба читати?На прилавках книжних крамниць ви можете знайти декілька вітчизняних та перекладених книг, присвячених програмуванню на мові Java. Більшість з них орієнтована на засоби JDK, створені фірмою Sun, та містять більш-менш докладний опис класів Java. Серед вдалих відзначимо книгу Стефана Дэвиса з назвою "Learn Java Now", що може розглядатися засобом навчання мові Java для тих, хто ніколи не програмував на С та С++. Серед перекладених книг, які можна зустріти в продажу, відзначимо книгу Джона Родли "Создание JAVA-апплетов". Ця книга розрахована на серйозних програмістів, які добре знають мову програмування Java. Однак для тих, хто тільки починає вивчати мову Java, вона може бути занадто складною. Інша книга, що заслуговує на увагу, це книга Криса Джамса з назвою "Java". Після невеликого вступу, розрахованого на тпочатківців, в цій книзі наводиться опис більш ніж дюжини достатньо цікавих аплетів з текст-джерелами та коментарями. Через мережу Internet вам досяжні не тільки безкоштовні засоби розробки прикладної програми та аплетів Java, але й грандіозні запаси документації та прикладів програм. Як відправну точки для вибору ви можете використовувати сервер розробника цієї мови - фірми Sun Microsystems, розташований за адресоюhttp://www.sun.com. Окрім документації та прикладів програм на Java, тут ви знайдете посилання на інші ресурси, присвячені цій мові програмування. Спробуйте також використувати пошукові сервера, такі як Jahoo! та Alta Vista, вказавши у вигляді ключового слово "Java". 1.2. Мобільність JavaВ свій час ви чули, що мова програмування С є машинонезалежною. Це потрібно розуміти в тому сенсі, що існує концептуальна можливість перенесення програм C на відмінні платформи. Однак слідує відзначити, що генерування прикладних програм, дійсно працюючих на різних платформах - непросте завдання. На жаль, справа не обмежується необхідністю перекомпілювання текст-джерела програми для роботи в іншому навколишньому середовищі. Багато задач виникає з несумісністю програмних інтерфейсів різних операційних систем та графічних оболонок, що реалізують інтерфейс користувача. Згадати хоча б проблеми, пов’язані з перенесенням 16-розрядних прикладних програм Windows в 32-розрядне навколишнє середовище Windows 95 та Windows NT. Навіть якщо ви ретельно слідували всім рекомендаціям, опрацьована таким чином прикладна програма ледве чи просто перекомпілюється без зміни ані рядка. Стан ще більше погіршується, якщо вам потрібне, наприклад, перенести текст-джерела використання Windows у середу керівної програми операційної системи OS/2 або до оболонки X-Windows керівної програми операційної системи UNIX. Адже ж є ще інші комп'ютери та робочі станції! Як неважко помітити, навіть якщо стандартизувати мову програмування для всіх платформ, задачі сумісності з програмним інтерфейсом операційної системи значно ускладнюють перенесення програм на інші платформи. Й, звичайно, ви не можете мріяти про те, щоб завантажувальний модуль однієї й тієї же програми міг працювати без виправлень у навколишньому середовищі іншої операційної системи та на відмінних платформах. Якщо програма підготована для процесора Intel, вона нізащо не буде здатна працювати на процесорі Alpha або якомусь іншому. Внаслідок цього, створюючи систему, здатну працювати на відмінних платформах, ви змушені дійсно робити декілька відмінних прикладних програм та супроводжувати їх окремо. На мал. 1 показано, як система, розроблена для Windows NT, переноситься на платформу Apple Macintosh.
Мал. 1. Перенесення системи з платформи Windows NT на платформу Macintosh Спочатку програміст готує текст-джерела системи для платформи Windows NT та налагоджує їх там. Для одержання завантажувального модуля текст-джерела компілюються та редагуються. Отриманий внаслідок завантажувальний модуль може працювати на процесорі фірми Intel в навколишньому середовищі операційної системи Windows NT. Для того, щоб перенести систему до середовища операційної системи комп'ютера Macintosh, програміст вносить необхідні виправлення в текст-джерела системи. Ці виправлення необхідні з-за перепадів у програмному інтерфейсі операційної системи Windows NT та операційної системи, встановленої на Macintosh. Далі ці текст-джерела транслюються та редагуються, внаслідок чого виходить завантажувальний модуль, здатний працювати у навколишньому середовищі Macintosh, але не здатний працювати у навколишньому середовищі Windows NT. Програма на мові Java компілюється в бінарний модуль, що складається з команд віртуального процесора Java. Такий модуль містить байт-код, призначений для виконання Java-інтерпретатором. На цей час вже створені перші взірці фізичного процесора, здатного виконувати цей байт-код, однак інтерпретатори Java існують на всіх базисних комп'ютерних платформах. Зрозуміло, на кожній платформі використовується свій інтерпретатор, або, точніше говорячи, свій віртуальний процесор Java. Якщо ваша система Java (або аплет) повинна працювати на декількох платформах, немає необхідності компілювати його текст-джерела декілька разів. Ви можете откомпілювати та відлагодити прикладну програму Java на одній, найбільш зручній для вас платформі. Внаслідок ви отримаєте байт-код, придатний для будь-якої платформи, де є віртуальний процесор Java. Сказане ілюструється на мал. 2.
Мал. 2. Підготовлення Java-програми для роботи на різних платформах Таким чином, Java-програма компілюється та налагоджується тільки один раз, що вже значно краще. Залишається, щоправда, питання - як бути з програмним інтерфейсом операційних систем, що відрізняються для різних платформ? Тут, на наш погляд, проектувальниками Java пропонується достатньо непогане рішення. Використання Java не звертається безпосереньо до інтерфейсу операційної системи. Замість цього воно користується готовими стандартними бібліотеками класів, що містять всі необхідне для організації інтерфейсу користувача, вибірки до файлів, для роботи в мережі та таке інше. Внутрішня реалізація бібліотек класів, зрозуміло, залежить від платформи. Однак всі завантажувальні модулі, що реалізують можливості цих бібліотек, поставляються в готовому виді разом з віртуальною машиною Java, тому програмісту не потрібне про це дбати. Для керівної програми операційної системи Windows, наприклад, поставляються бібліотеки динамічного завантаження DLL, всередині яких прихована вся працездатність стандартних класів Java. Абстрагуючись від апаратури на рівні бібліотек класів, програмісти можуть більше не дбати про перепади у реалізації програмного інтерфейсу конкретних керівних програм операційної системи. Це дозволяє створювати по-справжньому машинонезалежні прикладні програми. Ще одна задача, що виникає при перенесенні програм, складених на мові програмування С, полягає в тому, що розмір ділянки пам'яті, займаної змінними стандартних типів, різний для різних платформ. Наприклад, у навколишньому середовищі операційної системи Windows версії 3.1 змінна типу int у програмі, складеної на С, займає 16 біт. У середовищі Windows NT цей розмір складає 32 біти. Вочевидь, що тяжко складати програму, не знаючи точно, скільки існує біт у слові або у байті. При перенесенні програм на платформи з іншими характеристиками можуть виникати помилки, які іноді буде тяжко виявити. В мові Java всі базисні типи даних мають фіксований розмір, що не залежить від платформи. Тому програмісти завжди знають розміри змінних у своїй програмі. 1.3. Базисні типи данихУ мові Java визначено вісім базисних типів даних. Для кожного базисного типу даних відводиться конкретний розмір оперативної пам'яті. Цей розмір, як ми говорили в попередньому розділі, не залежить від платформи, на якій виконується Java-програма:
Фактичні розміри оперативної пам'яті, відведені для зберігання змінної, можуть відрізнятися від наведених вище, наприклад, для зберігання змінної типу short може бути зарезервовано слово розміром 32 біту. Однак мова Java зроблена таким чином, що це ніяк не вплине на мобільність використання. Таким чином оскільки у мові Java немає вказівників, ви не можете адресуватися до елементів масиву чисел за відносним зміщенням цих елементів у оперативній пам'яті. Отже, точний розмір елемента в даному випадку не грає ніякої ролі. Всі базисні типи даних за замовчанням ініціалізуються, тому програмісту не потрібно про це турбуватися. Ви можете також ініціалізувати змінні базисних типів в програмі або при їхньому визначенні, як це показане нижче: int nCounter=0; int i; i=8; Змінні типу boolean можуть постійно перебувати тільки в двох станах - true та false, причому ці стани жодним чином не можна співвідносити з цілими значеннями. Ви не можете, як це було в мові С, виконати перетворення типу boolean, наприклад, до типу int - компілятор видасть повідомлення про помилку. Змінна типу byte займає 8 біт оперативної пам'яті і про неї більше нічого сказати. Щодо типу 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 залишиться рівним нулю. Задачу можна розв'язати, якщо замість базисних змінних використовувати об'єкти вбудованих класів, відповідні базисним змінним. Про вбудовані класи ви дізнаєтеся з наступного розділу. 1.4. Бібліотеки класів JavaЯкщо надати у розпорядження програміста тільки мову програмування та не постачити його комплектом готових модулів, призначених для розв'язування самих розповсюджених задач, йому прийдеться відволікатися на множину дрібних елементів. Звичайно всі професійні системи проектування прикладної програми на мові програмування C++ містять в свойому складі комплект стандартних бібліотечних функцій або бібліотеки класів. В комплекті зі всіма засобами розробки Java поставляються достатньо розвинені бібліотеки класів, що значно спрощують процесс програмування. В цьому розділі ми стисло розповімо про склад та призначення бібліотек класів Java. 1.4.1. Вбудовані класиВ мові Java всі класи походять від класу Object, та, відповідно, успадковують методи цього класу. Деякі бібліотеки класів підключаються автоматично, та ми будемо називати їх вбудованими. До таких відносяться, зокрема, бібліотека з назвою java.lang. Інші бібліотеки класів ви повинні додавати до текст-джерела Java-програми явним чином за допомогою оператора import. 1.4.2. Що заміщає класи?Дуже часто в наших прикладних програмах замість базисних типів змінних ми будемо використовувати об'єкти вбудованих класів, що називаються класами, що заміщують (wrapper classes). Нижче ми перерахували назви цих класів та назви базисних типів даних, що вони заміщують:
Відмітимо, що для перетворення базисних типів даних в об'єкти класу, що заміщує, й зворотно ви не можете застосовувати оператор присвоєння. Замість цього необхідно використовувати відповідні розробники та методи класів, що заміщують. 1.4.3. Клас StringКлас String використовується для роботи з об'єктами, які представляють текстові рядки. Методи цього класу дозволяють виконувати над рядками практично всі операції, які ви робили раніше за допомогою бібліотечних функцій C. Це перетворення рядка в число, та зворотньо, з будь-якою специфікованою основою, визначення довжини рядка, порівняння рядків, виокремлення підрядка та таке інше. Хоча у мові Java не допускається перезавантаження (перевизначення) операторів, для об'єктів класу Stirng та об'єктів всіх класів, які походять від нього, зроблене вбудоване перезавантаження операторів "+" та "+=". За допомогою цих операторів можна виконувати зливання текстових рядків, наприклад: System. out. println ("x="+x+'\n)'; Тут у вигляді параметра функції println передається текстовий рядок, складений з трьох елементів: рядка "x=", числа x та знаку безумовного переходу на наступний рядок'\n'. Значення змінної x автоматично перетвориться в текстовий рядок (це виконується тільки для текстових рядків) та отриманий таким чином текстовий рядок об'єднується з рядком "x=". 1.4.4. Інші вбудовані класиСеред інших вбудованих класів відмітимо клас Math, призначений для виконання математичних операцій, таких як обчислення синуса, косинуса та тангенса. Передбачені також класи для виконання запуску процедур та трафіків, управління системою безпеки, а також для розв'язування інших систематичних завдань. Бібліотека вбудованих класів містить дуже важливі класи для роботи з вилученнями. Ці класи потрібні для оброблення помилкових режимів, що можуть виникнути (й виникають!) при роботі прикладної програми або аплетів Java. 1.4.5. Залучувані бібліотеки класівНижче ми стисло перелічимо залучувані бібліотеки класів для того, щоб ви могли оцінити можливості комплекту класів Java. Докладний опис цих класів є в довідковій системі Java WorkShop і в іншій літературі, присвяченій Java. Ми ж обмежимося описом тих класів, які будемо використовувати в наших зразках прикладної програми. 1.4.6. Бібліотека класів java.utilБібліотека класів java.util дуже корисна при підготовці прикладної програми, тому що в ній існують класи для генерування таких структур, як динамічні масиви, магазини і словники. Є класи для роботи з генератором псевдовипадкових чисел, для розбору рядків на елементи, для роботи з календарною датою та часом. 1.4.7. Бібліотека класів java.ioВ бібліотеці класів java.io зібрані класи, які мають відношення до введення та виведення даних через трафіки. Відмітимо, що з використанням цих класів можна працювати не тільки з трафіками байт, але також і з потоком даних інших типів, наприклад числами int або текстовими рядками. 1.4.8. Бібліотека класів java.netМова програмування Java розроблялася в припущенні, що нею будуть користуватися для генерування мережевих прикладних програм. Тому було б дивно, якби в складі середовища розробки Java-програм не постачалася бібліотека класів для роботи в мережі. Бібліотека класів java.net використовується якраз для цього. Вона містить класи, за допомогою яких можна працювати з універсальними мережевими адресами URL, передавати дані з використанням гнізд TCP і UDP, виконувати різні операції з адресами IP. Ця бібліотека містить також класи для виконання перетворень двійкових даних до буквено-цифрового формату, що часто буває необхідно. У вигляді прикладу, складеного на мові програмування Java та орієнтованого на роботу в мережі Internet, можна навести гру Java Color Lines. Це мережева версія відомої гри Lines, що виконана у вигляді декількох аплетів, взаємодіючих між собою та між сервером Web, на якому вони розміщені. Таким чином перелік імен гравців й досягнутих ними результатів запам'ятовується на сервері і ви можете взяти участь у світовому турнірі, позмагавшись з гравцями із різних країн. 1.4.9. Бібліотека класів java.awtДля генерування інтерфейсу користувача аплети Java можуть й повинні використовувати бібліотеку класів java.awt. AWT - це грубе від Abstract Window Toolkit (засоби для роботи з абстрактними частинами вікна). Класи, що входять до складу бібліотеки java.awt, надають можливість генерування інтерфейсу користувача засобом, що не залежить від платформи, на якій виконується аплет Java. Ви можете створювати звичайні частини вікна та діалогові панелі, клавіші, комутатори, переліки, меню, смуги відображення інформації, однорядкові та багаторядкові поля для введення літерно-цифрових даних. 1.4.10. Бібліотека класів java.awt.imageВ середовищі будь-якої операційної системи робота з графічними зображеннями буде достатньо складною задачею. В Windows для цього застосовується графічний інтерфейс GDI. Якщо ви будете малювати графічні зображення в середовищі OS/2 або X-Windows, вам, очевидно, доведеться використовувати інший програмний інтерфейс. Значну складність також викликає розбір заголовків графічних файлів, бо вони можуть мати відмінний формат й іноді містять неадекватні або суперечливі дані. Коли ви програмуєте на Java, малювання та оброблення графічних зображень виконується набагато простіше, якщо вам досяжна спеціально призначена для цього бібліотека класів java.awt.image. Окрім широкої різноманітності та вигоди визначених в ній класів й методів, відзначимо здатність цієї бібліотеки працювати з графічними зображеннями в форматі GIF. Цей формат широко використовується в Internet, бо він дозволяє ущільнювати файли графічних зображень в багато разів без втрати якості за рахунок вилучення надлишковості. 1.4.11. Бібліотека класів java.awt.peerБібліотека класів java.awt.peer служить для доопрацювання елементів AWT (наприклад, кнопок, переліків, полів редагування літерно-цифрових даних, перемикачів та таке інше) до реалізацій, що залежать від платформи, в процедурі генерування цих елементів. 1.4.12. Бібліотека класів java.appletЯк неважко здогадатися з назви, бібліотека класів java.applet інкапсулює виконання аплетів Java. Коли ви будете створювати свої аплети, вам буде потрібний клас Applet, розташований в цій бібліотеці класів. Додатково в бібліотеці класів java.applet визначені інтерфейси для доопрацювання аплетів до їхніх документів , що містять й класи для програвання звукових фрагментів. 1.5. Ось воно було, й - немаєНайбільша й шокуюча новина для тих, хто раніше програмував на С, а наразі приступив до вивчення 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 реалізований механізм масивів, що виключають необхідність використання вказівників. 1.6. Масиви в 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 будуть об'єктами деякого вбудованого класу. Для цього класу існує можливість визначити розмір масиву, звернувшись до елементу даних класу з ім'ям length, наприклад: int [] nAnotherNumbers; nAnotherNumbers=new int [15]; for (int i=0; i < nAnotherNumbers. length; i++) { nAnotherNumbers [i]=nInitialValue; } Для визначення розміру масиву вам не потрібний такий оператор, як sizeof, відомий нам всім з мови програмування С, тому що існує інший спосіб визначення цього розміру. 1.7. Збирання сміттяОдна з найцікавіших властивостей мови програмування Java й середовища виконання прикладної програми Java є спеціальні процедури збирання сміття, призначені для вилучення непотрібних об'єктів з оперативної пам'яті. Ця система позбавляє програміста від необхідності уважно стежити за використанням оперативної пам'яті, звільняючи непотрібні більш домени явним чином. Створюючи об'єкти в Java, ви можете керуватися принципом "створи й забудь", таким чином як система збирання сміття попіклується про вилучення ваших зайвих об'єктів. Об'єкт буде вилучений з оперативної пам'яті, як тільки на нього не залишиться жодного посилання з інших об'єктів. Пріоритет процедури збирання сміття дуже низький, тому "уборка" середовища виконання прикладної програми Java не віднімає ресурси у самої прикладної програми. 1.8. Ознаки реалізації класів в JavaЯкщо ви вмієте програмувати на мові С++, у вас не виникне жодних складнощів з програмуванням на Java, бо ці мови дуже схожі. Однак є й деякі відмінності, які належить враховувати. Ми наведемо стислий перелік цих відмінностей. 1.8.1. Визначення класуДля генерування класів ви можете використовувати тільки ключове слово class. Що до union, то це ключове слово в Java не застосовується. В мові програмування С++ опис класу може бути відділений від його визначення. Для Java це не так. Всі методи повинні бути визначені всередині визначення класу. Заборонено визначення вбудованих класів. В Java також немає взірців. Ви можете створювати класи тільки на базі інших класів. Об'єкт класу створюється за допомогою ключового слова new, однак ви не можете вилучити об'єкт явним чином, таким чином як ключове слово delete мови програмування С++ в Java не використовується. При визначенні класу ви не можете вказати руйнівник. Функції вилучення об'єктів Java з оперативної пам'яті виконує система збирання сміття. Всередині одного початкового файлу ви можете визначити тільки один загальнодоступний клас public. Всі класи в Java успадковуються від класу Object, тому для будь-якого об'єкту ви можете використовувати методи цього класу. 1.8.2. Визначення методівВи не можете визначати методи поза тілом класу, створюючи таким чином глобальні функції. Немає також можливості визначення поза класом глобальних даних. Тим не менш, всередині класу можна визначати статичні методи й поля (за допомогою ключового слова static), що будуть грати роль глобальних методів та даних. Користуючись ключовими словом static й final, можна визначати всередині класів глобальні сталі. Якщо у базовому класі метод, визначений з ключовим словом final, його не можна перевизначити в дочірньому класі, створеному на базі даного методу. Методи не можуть бути визначені як inline. Методи Java можуть створювати вилучення, викликані появою помилок або інших подій. Всі вилучення повинні або оброблятися всередині методу, або описуватися в визначенні методу після ключового слова throws. 1.8.3. Перевизначення операторівВ мові С++ ви могли перевизначити оператори, такі як +, -, ++ та таке інше. Мова Java не припускає перевизначення, що зроблене для спрощення програмування. Тим не менш, оператори "+" та "+=" перевантажені за замовчанням для виконання операції зливання текстових рядків класу String. 1.8.4. ІнтерфейсиІнтерфейси створюються за допомогою ключового слова interface таким же чином, як і класи. Однак, на відміну від останніх, інтерфейси будуть аналогом абстрактних базових класів без полів даних та передвизначені тільки для визначень комплекту методів для розв'язування будь-яких завдань, наприклад, розширення елемента у контейнери, організації переліків, сортування й таке інше. Ви можете створити свій клас на базі іншого класу, вказавши при цьому за допомогою ключового слова implements, що він реалізує той чи інший інтерфейс. При цьому поряд з методами базового класу у створеному таким чином класі будуть наявні методи, визначені в інтерфейсі. 1.8.5. Посилання на методи класуОскільки в Java немає вказівників, немає можливості посилатися на методи за допомогою оператора ->. Для звертання на метод класу використовується тільки оператор "крапка". Оператор"::" також не визначений в Java. Якщо вам необхідно викликати метод з базового класу, слідує використовувати ключове слово super. 1.8.6. СпадковістьЗа допомогою ключового слова extends ви можете успадкувати один клас (дочірній) від іншого (базисного). Багаторазова спадковість не допускається. Таким чином, для кожного дочірнього класу може бути тільки один базовий клас. У разі необхідності, однак, цей дочірній клас може здійснювати довільну кількість інтерфейсів. Для звертання на методи базового класу ви повинні використовувати ключове слово super. У разі необхідності ви можете викликати в першому виконуваному рядку розробника дочірнього класу розробник базового класу (знову ж за допомогою ключового слова super).
2. ПЕРША ПРИКЛАДНА ПРОГРАМА
Як ми вже говорили, прикладна програма Java може виконуватися під контролем спеціального інтерпретатора, працюючого у межах окремої процедури, або під контролем браузера. У першому випадку ми маємо діло з автономною прикладною Java-програмою, в другому - з аплетом. Вивчення програмування на Java ми почнемо з генерування примітивної автономної прикладної програми. В наступному розділі ми розкажемо про те, як зробити аплет й вкласти його у документ HTML. У вигляді інструментального засобу для розробки автономної прикладної програми й аплетів Java ми будемо використовувати інтегровану систему розробки Java WorkShop. Вона придатна для операційних систем Windows 95, Windows NT, Solaris (платформи SPARC та Intel). 2.1. Настроювання Java WorkShopДля настроювання Java WorkShop ви повинні вставити дистрибутивний диск до дисководу читання CD-ROM. Програма настроювання запутиться автоматично. Якщо цього не відбулося (наприклад, тому що автоматичний запуск заблокований), слідує виконати програму настроювання вручну. Якщо ви працюєте в середовищі Windows 95 або Windows NT, вам потрібне виконати програму setup.exe з директорії Win32. На дистрибутивному дискові Java WorkShop є також інсталяційні директорії для інших платформ. Процедура настроювання дуже проста. Достатньо слідувати командам, що з'являються на відображувальному екрані. Єдине, що необхідно вибрати - це маршрут до директорії, у яку будуть скопійовані файли Java WorkShop. При цьому слідує врахувати, що для настроювання Java WorkShop у середовищі Windows 95 або Windows NT вимагається приблизно 50 Мбайт дискової пам'яті. 2.2. Запуск Java WorkShopПісля завершення настроювання слідує витягти дистрибутивний компакт-диск та виконати Java WorkShop, скористувавшись графічним символом, створеної на робочому столі операційної системи. Коли система розробки Java WorkShop виконується вперше, вона вимагає ввести певне число. Ця число є на вкладиші у кожусі дистрибутивного компакт-диску. В тому разі, якщо ви розміщуєте демонстраційну версію Java WorkShop, послідовна число можна не вводити. При цьому ви зможите користуватися Java WorkShop безкоштовно 30 днів. Після запуску Java WorkShop на відображувальному екрані з'явиться головне вікно Java WorkShop, а також вікно Java WorkShop Startup. Якщо раніше ви ніколи не працювали з Java WorkShop, вікно Java WorkShop Startup допоможе вам освоїтися з базисними процедурами. Натиснувши відповідний клавішу, ви можете створити свій перший проект, відкрити приклад проекту, підготованого для вас фахівцями Sun Microsystems, ознайомитися з електронним довідником або довідковою системою, а також відкрити будь-який існуючий проект. Після надбання невеликої практики можна ввімкнути перемикач Don't show me this window at startup, та вікно Java WorkShop Startup перестане з'являтися всякий раз, коли ви запускаєте Java WorkShop. 2.3. Генерування нового проектуЯк і численні інші інтегровані системи розробки програмних засобів, система Java WorkShop використовує поняття проектів. Під проектом тут розуміється група файлів та параметрів, описаних в спеціальному файлі проектів. Всіма проектами в Java WorkShop керує керівник проектів. Щоб відкрити його вікно, показане на мал. 3, виберіть рядок Show Project Manager з меню Project головного вікна Java WorkShop.
Мал. 3. Вікно Java WorkShop Project Manager Всі проекти запам'ятовуються у портфелях (portfolios). В процедурі настроювання автоматично створюється ваш приватний портфель personal, що постійно перебує в каталозі My Portfolios. В цьому портфелі є проекти Checkers, CardFile, JellyBeanExample та Performance Example. При генеруванні нового проекту ми додамо його в портфель personal, хоча надалі ви зможите створювати нові портфелі. Перш ніж виконувати будь які операції над портфелем або проектом, потрібне зробити цей портфель або проект поточним. Щоб вибрати поточний портфель або проект, слідує зробити подвійний щиглик лівою клавішею мишки по відповідній назві. Рядок назви буде виділений синім кольорем. Для генерування проекту HelloJava виберіть з меню File вікна Java WorkShop Project Manager рядок New, а після цього в меню другого рівня рядок Project. Внаслідок буде виконаний майстер генерування проектів. У поле Please, name this project введіть назву створюваного проекту HelloJava, після цього треба ввімкнути перемикачі Standalone та No GUI. Перший з цих перемикачів специфікує тип проекту (автономна прикладна програма Java), другий вказує, що в проекті не використовуються засоби САПР інтерфейсу користувача. Далі натисніть клавішу Next. Ви потрапите до діалогової панелі Create Project. У заголовку цієї панелі ви повинні ввести маршрут до директорії, в якій будуть створюватися файли проекту. Після цього слідує ввімкнути перемикач No, вказуючи завдяки цьому, що директорія проектів пуста та в неї потрібне створити нові файли. Існує можливість генерування проектів на базі вже наявних файлів, однак доки ми цього робити не будемо. Тепер все готово до генерування проекту та ви можете натиснути клавішу Finish. Майстер проектів створить всі необхідні файли та зробить новий проект поточним. Основний файл проекту потрапить введеним в дію в головне вікно прикладної програми Java WorkShop. Тут ви можете його редагувати. Якщо тепер ви відкриєте вікно Java WorkShop Project Manager, то побачите, що у портфелі personal з'явився новий проект HelloJava. Відкривши його, можна побачити файли, що входять до нього (мал. 4).
Мал. 4. Файли проекту HelloJava. Наш проект містить тільки один файл з ім'ям HelloJava.java. Відредагуйте цей файл, додавши до нього наступний рядок: System. out. println ("Hello, Java! "); Тепер виберіть з меню Build головного вікна Java WorkShop рядок Build All. Через декілька миттевостей текст-джерела прикладної програми будуть відтрансльований. Результат ви зможите побачити на вашій сторінці Build блокноту, розташованого у низу вікна Java WorkShop. Щоб виконати прикладну програму, виберіть з меню Project рядок Run. На відображувальному екрані з'явиться консоль прикладної програми з запитом "Hello, Java!" (рис. 5).
Мал. 5. Дія прикладної програми Hello, Java! 2.4. Текст-джерело прикладної програми HelloJavaТекст-джерело нашої першої прикладної програми складається всього з декількох рядків: public class HelloJava { public static void main (String args []) { System. out. println ("Hello, Java! "); } } По своїй простоті він не поступається відомій програмі "Hello, world!", з якої звичайно запускають вивчення мови програмування C. В нашій прикладній програмі визначений один клас типу public з ім'ям HelloJava. Спостержемо, що початковий файл прикладної програми Java може містити тільки один клас public, причому ім'я файлу повинно в точності співпадати з ім'ям такого класу. В даному випадку початковий файл називається HelloJava.java. Якби ви назвали файл helloJava.java, компілятор видав би повідомлення про помилку. У класі HelloJava віртуозом проектів автоматично створюється один статичний метод з ім'ям main. Якщо клас типу public з ім'ям, паралельно до ім'я файлу, містить визначення методу main, то такий метод заступає точкою входу автономної прикладної програми Java. В цьому він нагадує функцію main звичайної програми, складеної на мові програмування C. У вигляді параметра методу main передається звертання на масив рядків класу String. Через ці рядки ви можете передавати прикладній програмі Java параметри для початку. Як наша прикладна програма виводить текстовий рядок на консоль? У класі System визначена змінна класу PrintStream з ім'ям out. У класі PrintStream визначений метод println, за допомогою якого прикладна програма HelloJava виводить повідомлення "Hello, Java!" на консоль. Але де ж об'єкт, для якого викликається метод println? В класі System поле PrintStream визначене як статичне, тому методи цього класу можна викликати, не створюючи об'єктів класу System.
3. ПЕРШИЙ АПЛЕТ
В попередньому розділі ми створювали автономну прикладну програму Java, працюючу під контролем віртуальної машини Java. Тепер ми створимо прикладну програму іншого типу - аплет. Аплет Java теж виконується під контролем віртуальної машини Java, але вбудованої у браузер. Коли браузер вводить в дію у своє вікно документ HTML з аплетом, байт-код аплета починає свою роботу. Зовні аплет має вигляд вікна специфікованого розміру. Він може малювати всередині цього вікна (та тільки в ньому) довільні графічні фігури та текст. Файл двійкових кодів з інтерпретуючим байт-кодом Java розміщується на сервері Web. В документі HTML за допомогою оператора <APPLET> організується звертання на цей файл двійкових кодів. Коли користувач вводить в дію у браузер документ HTML з аплетом, файл аплета перезаписується з сервера Web на робочу станцію користувача. Після цього браузер запускає його виконання. Можливо, вам не сподобається така ідея, як запуск чужого аплета на свойому комп'ютері - мало чи чого цей аплет може там зробити. Однак аплети, на відміну від звичайної прикладної програми Java, сильно обмежені в своїх правах. Наприклад, вони не можуть читати локальні файли і тим більше в них писати. Є також обмеження й на обмін даними через мережу: аплет може обмінюватися даними тільки з тим сервером Web, з якого він був введений в дію. 3.1. Генерування проекту аплетаПроект аплета створюється таким же чином, як й проект автономної прикладної програми Java, однак майстру проектів необхідно вказати інші параметри. В першій діалоговій панелі майстра проектів слідує ввімкнути перемикачі Applet та No GUI. Зробивши це, натисніть кнопку Next. На відображувальному екрані з'явиться друга діалогова панель майстра проектів. Тут ви повинні вказати шлях до директорії, куди майстер проектів запише файли проекту, а також ввімкнути перемикач No. Внаслідок цього майстер проектів створить текст-джерела аплета, а також додасть новий проект в портфель personal (якщо цей портфель залишився активним востаннє, коли ви виконували Java WorkShop). Новий проект називається HelloApplet (мал. 6).
Мал. 6. Новий проект з'явився в активному портфелі personal Текст-джерело аплета буде створене автоматично та введене в дію у вікно редагування системи Java WorkShop. Ви можете відтранслювати отримане текст-джерело й запустити аплет на виконання. Він буде працювати під контролем підсистеми перегляду аплетів appletviewer, що входить до складу Java WorkShop. Доки у вікні нашого аплета нічого немає (мал. 7), однак скоро ми виправимо це положення.
Рис. 5. Вікно аплета, створеного автоматично майстром проектів 3.2. Текст-джерело аплетаПовне текст-джерело аплета, створене автоматично майстром проектів Java WorkShop, ми представили у роздруку нижче. /* Файл HelloApplet.java *\ import java.applet.Applet; public class HelloApplet extends Applet { /** Initializes the applet. You never need to call this directly; it is called automatically by the system once the applet is created. / public void init () {} /** Called to start the applet. You never need to call this directly; it is called when the applet's document is visited. / public void start () {} /** Called to stop the applet. This is called when the applet's document is no longer on the screen. It is guaranteed to be called before destroy () is called. You never need to call this method directly / public void stop () {} /** Cleans up whatever resources are being held. If the applet is active it is stopped. / public void destroy () {} } Через багату кількість коментарів (при певному рівні знання англійської) ви можете подумати, що текст-джерело аплета, який нічого не виконує, занадто складний. Однак це зовсім не так. От що залишеться, якщо ми приберемо всі коментарі: import java.applet.Applet; public class HelloApplet extends Applet { public void init () {} public void start () {} public void stop () {} public void destroy () {} } Текст-джерело нашого аплета починається з рядка, який залучає оператором import бібліотеку класів java.applet.Applet. Оператор import повинен розміщуватися у файлі текст-джерела перед іншими операторами (за винятком операторів коментаря). У вигляді параметра оператору import передається ім'я залучуваного класу з бібліотеки класів. Якщо ж необхідно увімкнути всі класи даної бібліотеки, замість ім'я класу вказується знак "". Нагадаємо, що бібліотека java.applet.Applet містить класи, необхідні для генерування аплетів, тобто різноманітність прикладної програми Java, вбудовованої у документи HTML та працюючої під керуванням браузера Internet. Ще одна бібліотека класів, що вам скоро знадобиться, це java.awt. З її допомогою аплет може виконувати в свойому вікні малювання різних графічних фігур або тексту. Переваги даного методу перед використанням для малювання традиційного програмного інтерфейсу операційної системи беруться в тому, що він працює на будь-якій комп'ютерній платформі. Далі в текст-джерелі аплета визначиться клас типу public з ім'ям HelloApplet. Нагадаємо, що це ім'я повинно обов'язково співпадати з ім'ям файлу, цього класу , що містить текст-джерело. public class HelloApplet extends Applet { ... } Визначений нами клас HelloApplet за допомогою ключового слова extends успадковується від класу Applet. При цьому методам класу HelloApplet стають досяжними всі методи та дані класу, за винятком визначених як private. Клас Applet визначений в бібліотеці класів java.applet.Applet, яку ми увімкнули оператором import. 3.3. Методи в класі HelloAppletСтворюючи файл HelloApplet.java, майстер проектів системи Java WorkShop визначила в класі HelloApplet декілька методів, замінивши таким чином деякі методи базового класу Applet. 3.3.1. Метод initМетод init визначений у базовому класі Applet, від якого успадковуються всі аплети. Визначення його таке, що цей метод рівним рахунком нічого не робить(!?). Коли викликається метод init й, нарешті, навіщо він потрібний? Метод init викликається тоді, коли браузер вводить в дію у своє вікно документ HTML з оператором <APPLET>, аби посилатися на даний аплет. В цей момент аплет може виконувати присвоювання початкових значень, або, наприклад, створювати трафіки, якщо він працює в багатопроцесному режимі. Існує противага для методу init - метод destroy. Про нього ми розкажемо нижче. 3.3.2. Метод destroyПеред вилученням аплета з оперативної пам'яті викликається метод destroy, що визначений в базовому класі Applet як порожня затичка. Майстер проектів додає в текст-джерело класу перевизначення методу destroy, який ви можете у разі необхідності змінити. Методу destroy звичайно доручають всі необхідні операції, що слідує виконати перед вилученням аплету. Наприклад, якщо в методі init ви утворювали якісь трафіки, у методі destroy їх потрібне завершити. 3.3.3. Метод startМетод start викликається після методу init в момент, коли користувач запускає переглядати документ HTML з вбудованим у нього аплетом. Ви можете модифікувати текст цього методу, якщо при кожному відвіданні користувачем сторінки з аплетом необхідно виконувати будь-яке присвоювання початкових значень. 3.3.4. Метод stopДодатком до методу start виступає метод stop. Він одержує контроль, коли користувач покидає сторінку з аплетом та вводить в дію у вікно браузера іншу сторінку. Відмітимо, що метод stop викликається перед методом destroy. 3.4. Текст-джерело документа HTMLОкрім файлу текст-джерела аплета майстер проектів створив файл документа HTML HelloApplet.tmp.html, наведений в роздруку нижче. /Файл HelloApplet.tmp.html\ <applet name="HelloApplet" code="HelloApplet" codebase="file:/e:/sun/vol3/src/HelloApplet" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here."> </applet> За допомогою оператора <applet> наш аплет вбудовується у цей документ. Оператор <APPLET> використовується в парі з оператором </APPLET> та має наступні параметри:
Додатково між операторами <APPLET> і </APPLET> ви можете специфікувати параметри аплета. Для цього використовується оператор <PARAM>, що ми розглянемо пізніше. В нашому випадку специфікатор формату NAME і ім'я файлу двійкових кодів специфіковані майстром проекту як "HelloApplet": name="HelloApplet" code="HelloApplet" Параметр CODEBASE специфікує шлях до директорії локального накопичувача: codebase="file:/e:/sun/vol3/src/HelloApplet" Коли ви будете розподіляти ресурси для документу HTML на сервері, параметр CODEBASE необхідно змінити або вилучити, якщо цей документ та аплет розміщуються в одній директорії. В будь-якому випадку, якщо параметр CODEBASE специфікований, він повинен вказувати адресу URL директорії з аплетом. 3.5. Змінюємо текст-джерело аплетаТепер давайте спробуємо трохи змінити текст-джерело аплета, щоб змусити його малювати в своєму вікні текстовий рядок "Hello, Java world", використовуючи клас Graphics. Тут ми навмисне вносили до текст-джерела помилку, щоб показати, як Java WorkShop відреагує на неї. Як буде видно, повідомлення про помилку відображається на сторінці блокноту з назвою Build. Текст повідомлення стверджує, що компілятор не взмозі знайти визначення класу Graphics, на який є звертання в дев'ятому рядку. Додамо рядок імпортування класу java.awt., як це показане в роздруку нижче. /* Файл HelloApplet. java (новий варіант) *\ import java.applet.Applet; import java.awt.; public class HelloApplet extends Applet { public String getAppletInfo () { return "HelloJava Applet"; } public void paint (Graphics g) { g. drawString ("Hello, Java world!", 20, 20); } } Тепер текст-джерело аплета транслюється без помилок. Якщо запустити аплет на виконання, в його вікні буде намальований рядок "Hello, Java world" (мал. 8).
Мал. 8. Тепер наш аплет "вміє" малювати в свойому вікні текстові рядки 3.6. Метод paintМабуть, найбільш цікавим для вас буде метод paint, що виконує малювання у вікні аплета. От його текст-джерело: public void paint (Graphics g) { g. drawString ("Hello, Java world!", 20, 20); } Якщо подивитися визначення класу Applet, що постійно перебує в файлі JavaWorkshop20\JDK\src\java\applet\Applet.java, то в ньому немає методу paint. В якому ж класі визначений цей метод? 3.6.1. Ієрархія класівЗазирнемо до документації. Виберіть з меню Help головного вікна прикладної програми Java WorkShop рядок Java API Documentation. На відображувальному екрані з'явиться вікно браузера, вбудованого до Java WorkShop. За допомогою цього браузера ви зможите переглядати зміст довідкової системи. У розділі Java API Packages виберіть бібліотеку класів java.applet, а після цього в розділі Class Index - рядок Applet. Ви побачите ієрархію класів: java.lang.Object | +- --java.awt.Component | +- --java.awt.Container | +- --java.awt.Panel | +- --java.applet.Applet З цієї ієрархії видно, що клас java.applet. Applet походить від класу java.awt.Panel. Цей клас, в свою чергу, визначений в бібліотеці класів java.awt та пішов від класу java.awt.Container. Продовжимо наші дослідження. В класі java.awt.Container знову немає методу paint, але саме цей клас створений на базі класу java.awt.Component. Але й тут методу paint немає. Цей метод визначений в класі java.awt.Component, що, в свою чергу, пішов від класу java.lang.Object та реалізує інтерфейс java.awt.image.ImageObserver. Таким чином ми трасували ієрархію класів від класу java.applet.Applet, на базі якого створений наш аплет, до класу java.lang.Object, що буде базисним для всіх класів в Java. Метод paint визначений в класі java.awt.Component, але таким чином, що коли цей клас буде базисним для класу Applet, та для нашого класу HelloApplet, ми можемо перевизначити метод paint. 3.6.2. Виклик методу paintМетод paint викликається, коли необхідно перемалювати вікно аплета. Якщо ви утворювали прикладну програму для операційної системи 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 ("Hello, Java world!", 20, 20); Ось дослідний зразок цього методу: public abstract void drawString (String str, int x, int y); Через перший параметр методу drawString передається текстовий рядок у вигляді об'єкту класу String. Другий та третій параметр визначають, відповідно, координати крапки, в якій почнеться малювання рядка. 3.6.3. Система координатАплети використовують систему координат, що узгоджує режим відображення MM_TEXT, знайомому тим, хто створював прикладну програму Windows. Початок цієї системи координат розміщений в лівому верхньому куту вікна аплета, вісь X спрямована зліва направо, а вісь Y - зверху до низу (мал. 9).
Мал. 9. Система координат, що використовується методом drawString На цьому же відображенні показано, як метод drawString намалює текстовий рядок з координатами (xCoord, yCoord). 3.7. Метод getAppletInfoБазовий клас Applet містить визначення методу getAppletInfo, повертаюче значення null. В нашому класі HelloApplet, що буде дочірнім по відношенню до класу Applet, ми перевизначили метод getAppletInfo з базового класу наступним чином: public String getAppletInfo () { return "HelloJava Applet"; } Тепер метод getAppletInfo повертає літерно-цифрові дані про аплет у вигляді об'єкту класу String. Цими даними можуть скористуватися інші аплети або сценарії JavaScript, наприклад, для визначення можливості взаємодії з аплетом.
4. МАЛЮВАННЯ У ВІКНІ АПЛЕТУ
В попередньому розділі ми навели примітивний приклад аплету, що виконує малювання текстового рядка в свойому вікні. Тепер ми розповімо вам про те, що й як може малювати аплет. Засіб, яким аплет виконує малювання в свойому вікні, повністю відрізняється від того, яким користуються програми MS-DOS. Натомість щоб звертатися прямо або через драйвер до регістрів відеоконтролера, аплет користується методами з класу Graphics. Ці методи інкапсулюють всі ознаки апаратури, надаючи в розпорядження програміста засіб малювання, що придатний для будь-якої комп'ютерної платформи. Для вікна аплету створюється об'єкт класу Graphics, звертання на який передається методу paint. Раніше ми вже користувались цим об'єктом, викликаючи для нього метод drawString, який малює у вікні текстовий рядок. Об'єкт, звертання на який передається методу paint, й є об'єм відображення. Зараз ми займемося об'ємом відображення впритул. 4.1. Об'єм відображенняПростіше всього уявити собі об'єм відображення як полотно, на якому малює митець. Точно таким же чином, як митець може вибирати для малювання різні інструменти, програміст, що створює аплет Java, може вибирати різні методи класу Graphics та специфікувати різні властивості об'єму відображення. 4.1.1. Методи класу GraphicsУ вигляді базисного для класу Graphics (повна назва класу java.awt.Graphics) виступає клас java.lang.Object. Перш за все ми наведемо дослідний зразок розробника цього класу та його методів з стислими коментарями. Повний опис ви зможите знайти в електронній документації, що входить до комплекту Java WorkShop. Далі ми розглянемо встановлення базисних методів, згрупувавши їх за виконуваними функціями. 4.1.2. Розробник Graphicsprotected Graphics (); 4.1.3. Метод clearRectВилучення змісту прямокутної зони public abstract void clearRect (int x, int y, int width, int height); 4.1.4. Метод clipRectВизначення зони обмеження виведення public abstract void clipRect (int x, int y, int width, int height); 4.1.5. Метод copyAreaКопіювання змісту прямокутної зони public abstract void copyArea (int x, int y, int width, int height, int dx, int dy); 4.1.6. Метод createГенерування об'єму відображення public abstract Graphics create (); public Graphics create (int x, int y, int width, int height); 4.1.7. Метод disposeВилучення об'єму відображення public abstract void dispose (); 4.1.8. Метод draw3DRectМалювання прямокутної зони з тримірним виділенням public void draw3DRect (int x, int y, int width, int height, boolean raised); 4.1.9. Метод drawArcМалювання сегменту public abstract void drawArc (int x, int y, int width, int height, int startAngle, int arcAngle); 4.1.10. Метод drawBytesМалювання тексту з масиву байт public void drawBytes (byte data [], int offset, int length, int x, int y); 4.1.11. Метод drawCharsМалювання тексту з масиву літер public void drawChars (char data [], int offset, int length, int x, int y); 4.1.12. Метод drawImageМалювання растрового відображення 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); 4.1.13. Метод drawLineМалювання рядка public abstract void drawLine (int x1, int y1, int x2, int y2); 4.1.14. drawOvalМалювання овалу public abstract void drawOval (int x, int y, int width, int height); 4.1.15. Метод drawPolygonМалювання багатокутника public abstract void drawPolygon ( int xPoints [], int yPoints [], int nPoints); public void drawPolygon (Polygon p); 4.1.16. Метод drawRectМалювання вікна public void drawRect (int x, int y, int width, int height); 4.1.17. Метод drawRoundRectМалювання вікна з закругленими кутами public abstract void drawRoundRect ( int x, int y, int width, int height, int arcWidth, int arcHeight); 4.1.18. Метод drawStringМалювання текстового рядка public abstract void drawString (String str, int x, int y); 4.1.19. Метод fill3DRectМалювання заповненого вікна з тримірним виділенням public void fill3DRect (int x, int y, int width, int height, boolean raised); 4.1.20. Метод fillArcМалювання заповненого сегменту кола public abstract void fillArc (int x, int y, int width, int height, int startAngle, int arcAngle); 4.1.21. Метод fillOvalМалювання заповненого овалу public abstract void fillOval (int x, int y, int width, int height); 4.1.22. Метод fillPolygonМалювання заповненого багатокутника public abstract void fillPolygon ( int xPoints [], int yPoints [], int nPoints); 4.1.23. Метод fillPolygonМалювання заповненого многокутника public void fillPolygon (Polygon p); public abstract void fillRect (int x, int y, int width, int height); 4.1.24. Метод fillRoundRectМалювання заповненого вікна з закругленими кутами public abstract void fillRoundRect ( int x, int y, int width, int height, int arcWidth, int arcHeight); 4.1.25. Метод finalizeТрасування виклику методу dispose public void finalize (); 4.1.26. Метод getClipRectВизначення конвертів обмеження виводу public abstract Rectangle getClipRect (); 4.1.27. Метод getColorВизначення кольору, вибраної в об'ємі відображення public abstract Color getColor (); 4.1.28. Метод getFontВизначення шрифту, вибраного в об'ємі відображення public abstract Font getFont (); 4.1.29. Метод getFontMetricsВизначення метрик поточного шрифту public FontMetrics getFontMetrics (); 4.1.30. Метод getFontMetricsВизначення метрик специфікованого шрифту public abstract FontMetrics getFontMetrics (Font f); 4.1.31. Метод setColorНастроювання кольору для малювання у об'ємі відображення public abstract void setColor (Color c); 4.1.32. Метод setFontНастроювання поточного шрифту в об'ємі відображення public abstract void setFont (Font font); 4.1.33. Метод setPaintModeМетод setPaintMode встановлює в об'ємі відображення режим малювання, при якому виконується заміна відображення поточним кольором, встановленим в об'ємі відображення. public abstract void setPaintMode (); 4.1.34. Метод setXORModeНастроювання комбінації розрядів для малювання. Специфікуючи комбінацію розрядів для малювання за допомогою методу setXORMode, ви можете виконати при малюванні заміна поточного кольору на колір, зазначений в параметрі методу, й навпаки, кольору, зазначеного в параметрі методу, на поточний. Всі інші кольори змінюються непередвіщеним чином, однак ця операція зворотна, якщо ви намалюєте ту же саму фігуру два рази на одному й тому ж місці. public abstract void setXORMode (Color c1); 4.1.35. Метод translateВідхилення почала системи координат. Метод translate зсує початок системи координат в об'ємі відображення таким чином, що воно переміщується до пункту з координатами (x, y), специфікованими через параметри методу: public abstract void translate (int x, int y); 4.1.36. Метод toStringОдержання текстового рядка, становлячого даний об'єм відображення public String toString (); 1.4.2. Настроювання властивостей об'єму відображенняЗмінюючи властивості об'єму відображення, прикладна програма Java може встановити колір для малювання графічних зображень, таких як рядки та багатокутники, шрифт для малювання тексту, режим малювання та комбінацію розрядів. Можливе також зміщення початку системи координат. 1.4.2.1. Варіант кольоруЗаміна кольору, вибраного в об'ємі відображення, виконується достатньо часто. В класі Graphics для заміни кольору визначений метод setColor, дослідний зразок якого уявлений нижче: public abstract void setColor (Color c); У вигляді параметра методу setColor передається звертання на об'єкт класу Color, за допомогою якого можна вибрати той чи інший колір. 1.4.2.2. Як специфікується колір?Для цього можна використовувати декілька способів. Перш за все, вам досяжні статичні об'єкти, що визначать фіксований комплект базисних кольорів:
Цим комплектом кольорів користуватися дуже просто: public void paint (Graphics g) { g.setColor (Color.yellow); g.drawString ("Hello, Java world!", 10, 20); ... } Тут ми навели фрагмент текст-джерела методу paint, в якому в контексті відображення встановлюється жовтий колір. Після цього метод drawString виведе текстовий рядок" Hello, Java world!" жовтим кольором. Якщо необхідне більш точне настроювання кольору, ви можете скористуватися одним з трьох розробників об'єкту 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 визначене ще декілька методів, що можуть стати вам корисними:
Другий спосіб настроювання кольорового фону та відображення береться у виклику методів setBackground та setForeground, наприклад: setBackground (Color.yellow); setForeground (Color.black); Тут ми встановлюємо для вікна аплету жовтий колір фону та чорний колір відображення. 1.4.2.3. Вибір шрифтуЗа допомогою методу setFont з класу Graphics ви можете вибрати в об'ємі відображення шрифт, що буде використовуватися методами drawString, drawBytes та drawChars для малювання тексту. Нижче маємо дослідний зразок методу setFont: public abstract void setFont (Font font); У вигляді параметра методу setFont слідує передати об'єкт класу Font. Наведемо стислий перелік полів, розробників та методів цього класу. 1.4.2.3.1. name protected String name; 1.4.2.3.2. size protected int size; 1.4.2.3.3. style protected int style; Бітові комбінації розрядів стиля шрифту 1.4.2.3.4. BOLD public final static int BOLD; 1.4.2.3.5. ITALIC public final static int ITALIC; 1.4.2.3.6. PLAIN public final static int PLAIN; 1.4.2.3.7. Розробники public Font (String name, int style, int size); 1.4.2.3.8. Метод equals Порівнювання шрифтів public boolean equals (Object obj); 1.4.2.3.9. Метод getFamily Визначення назви сімейства шрифтів public String getFamily (); 1.4.2.3.10. Метод getFont Одержання шрифту по його властивостях public static Font getFont (String nm); public static Font getFont (String nm, Font font); 1.4.2.3.11. Метод getName Визначення назви фонту public String getName (); 1.4.2.3.12. Метод getSize Визначення розміру шрифту public int getSize (); 1.4.2.3.13. Метод getStyle Визначення стиля шрифту public int getStyle (); 1.4.2.3.14. Метод hashCode Одержання геш-коду шрифту public int hashCode (); 1.4.2.3.15. Метод isBold Визначення жирності шрифту public boolean isBold (); 1.4.2.3.16. Метод isItalic Перевірка, буде чи фонт похилим public boolean isItalic (); 1.4.2.3.17. Метод isPlain Перевірка, чи є шрифтове виділення public boolean isPlain (); 1.4.2.3.18. Метод toString Збирання текстового рядка для об'єкту public String toString (); Створюючи шрифт розробником Font, ви повинні вказати ім'я, стиль та розмір шрифту. У вигляді ім'я можна вказати, наприклад, такі рядки як Helvetica або Courier. Врахуйте, що в системі віддаленого користувача, який завантажує ваш аплет, може не знайтися шрифту з означеним вами ім'ям. В цьому випадку браузер замінить його на найбільш підхожий (з його точки зору). Стиль шрифту специфікується комбінаціями розрядів BOLD, ITALIC та PLAIN, що можна комбінувати за допомогою булевої операції "або":
Що до розміру фонту, то він вказується в точках растру. 1.4.3. Визначення властивостей об'єму відображенняРядок методів класу Graphics дозволяє визначити різні властивості об'єму відображення, наприклад, колір, вибраний в об'ємі відображення або метрики поточного шрифту, яким виконується малювання тексту. Розглянемо методи, що дозволять визначити властивості об'єму відображення. 1.4.3.1. Визначення меж зони обмеження виведенняЗа допомогою методу clipRect, про який ми розкажемо пізніше, ви можете визначити у вікні аплету зону обмеження виведення прямокутної форми. Поза цією зоною малювання графічних зображень та тексту не виконується. Метод getClipRect дозволяє вам визначити координати поточної зони обмеження, специфікованої в об'ємі відображення: public abstract Rectangle getClipRect (); Метод повертає звертання на об'єкт класу Rectangle, що, зокрема, має поля класу з іменами x, y, height та width. В цих полях постійно перебувають, відповідно, координати верхнього лівого кута, висота та ширина прямокутної зони. 1.4.3.2. Визначення кольору, вибраноого в об'ємі відображенняМетод getColor повертає звертання на об'єкт класу Color, установлюючи поточний колір, вибраний в об'ємі відображення: public abstract Color getColor (); 1.4.3.3. Визначення шрифту, вибраного в об'ємі відображенняЗа допомогою методу getFont, повертаючого звертання на об'єкт класу Font, ви можете визначити поточний шрифт, вибраний в об'ємі відображення: public abstract Font getFont (); 1.4.3.4. Визначення метрик поточного шрифтуНезважючи на те, що ви можете замовити шрифт зі специфікованим ім'ям та розміром, не варто сподіватися, що вікно перегляду виділить вам саме такий шрифт, який ви просили. Для вірогідного призначення тексту та інших графічного символу у вікні аплету вам необхідно знати метрики дійсного шрифту, вибраного вікном перегляду в об'ємі відображення. Метрики поточного шрифту в об'ємі відображення ви можете дізнатися за допомогою методу getFontMetrics, дослідний зразок якого наведений нижче: public FontMetrics getFontMetrics (); Метод getFontMetrics повертає звертання на об'єкт класу FontMetrics. Нижче ми навели перелік найбільш важливих методів цього класу, призначених для одержання окремих параметрів шрифту:
Зверніть увагу на метод stringWidth, що дозволить визначити ширину текстового рядка. Відмітимо, що без цього методу визначення ширини текстового рядка було б непростою задачею, особливо якщо шрифт має змінну ширину літер. Для визначення повної висоти рядка символів ви можете скористуватися методом getHeight. 1.4.3.5. Визначення метрик специфікованого шрифтуМетод getFontMetrics з параметром типу Font дозволяє визначити метрики будь-якого шрифту, що передаються йому у вигляді параметра: public abstract FontMetrics getFontMetrics (Font f); На відміну від нього, метод getFontMetrics без параметрів повертає метрики поточного шрифту, вибраного в об'ємі відображення. 1.4.4. Малювання геометричних фігурВ цьому розділі ми опишемо методи класу Graphics, призначені для малювання елементарних геометричних фігур, таких як рядки, прямокутники, кола та таке інше. 1.4.4.1. ЛініїДля того, щоб намалювати пряму тонку суцільну лінію, ви можете скористуватися методом drawLine, дослідний зразок якого наведений нижче: public abstract void drawLine (int x1, int y1, int x2, int y2); Кінцеві точки лінії мають координати (x1, y1) та (x2, y2), як це показано на мал. 10.
Мал. 10. Малювання прямої лінії На жаль, в об'ємі відображення не передбачені жодні властивості, що дозволять намалювати пунктирну лінію або лінію збільшеної товщини. 1.4.4.2. Прямокутники й квадратиСеред методів класу Graphics є декілька, призначених для малювання прямокутників. Перший з них, з ім'ям drawRect, дозволяє намалювати вікно, специфікований координатами свого лівого верхнього кута, шириною та висотою: public void drawRect (int x, int y, int width, int height); Параметри x та y специфікують, відповідно, координати верхнього лівого кута, а параметри width й height - висоту й ширину вікна (мал. 11).
Мал. 11. Малювання вікна На відміну від методу drawRect, що малює тільки прямокутне вікно, метод fillRect малює заповнене вікно. Для малювання й заповнення вікна використовується колір, вибраний в об'ємі відображення (мал. 12).
Мал. 12. Малювання заповненого вікна Дослідний зразок методу fillRect наведений нижче: public abstract void fillRect (int x, int y, int width, int height); Метод drawRoundRect дозволяє намалювати вікно з закругленими кутами: public abstract void drawRoundRect (int x, int y, int width, int height, int arcWidth, int arcHeight); Параметри x та y визначають координати верхнього лівого кута вікна, параметри width і height специфікують, відповідно його ширину й висоту. Розміри еліпса, що приєднає зкривлення по кутах, ви можете специфікувати за допомогою параметрів arcWidth та arcHeight. Перший з них специфікує ширину еліпса, а другий - висоту (мал. 13).
Мал. 13. Малювання вікна з закругленими кутами Метод fillRoundRect дозволяє намалювати заповнене вікно з закругленими кутами (мал. 14).
Мал. 14. Малювання заповненого вікна з закругленими кутами Встановлення параметрів цього методу аналогічно встановленню параметрів щойно розглянутого методу drawRoundRect: public abstract void fillRoundRect (int x, int y, int width, int height, int arcWidth, int arcHeight); Метод fill3Drect передвизначений для малювання або западистого вікна , що виступає: public void fill3DRect (int x, int y, int width, int height, boolean raised); Якщо значення параметра raised дорівнює true, малюється вікно , що виступає, якщо false - западисте. Встановлення інших параметрів аналогічно встановленню параметрів методу drawRect. 1.4.4.3. БагатокутникиДля малювання багатокутників в класі Graphics передбачене чотири методи, два з яких малюють порожні багатокутники, а два - заповнені. Перший метод малює порожній багатокутник, специфікований масивами координат по вісі X та Y: public abstract void drawPolygon ( int xPoints [], int yPoints [], int nPoints); Через параметри xPoints й yPoints передаються, відповідно, посилання на масиви координат по вісі X й Y. Параметр nPoints специфікує кількість пунктів у масивах. На мал. 15 показаний багатокутник, змальований методом drawPolygon.
Мал. 15. Багатокутник, мальований методом drawPolygon В цьому багатокутникові шість вершин з координатами від (x0, y0) до (x5, y5), причому для того, щоб він став замкненим, координати першої та останньої вершини співпадають. Другий метод також малює порожній багатокутник, однак у вигляді параметра методу передається звертання на об'єкт Polygon: public void drawPolygon (Polygon p); Клас Polygon достатньо простий. Наведемо опис його полів, розробників та методів: 1.4.4.3.1. Поля класу •npoints Кількість вершин public int npoints; •xpoints Масив координат по вісі X public int xpoints []; •ypoints 1.4.4.3.2. Масив координат по вісі Y public int ypoints [] 1.4.4.3.3. Розробники public Polygon (); public Polygon (int xpoints [], int ypoints [], int npoints); 1.4.4.3.4. Методи •addPoint Розширення вершини public void addPoint (int x, int y); •getBoundingBox Одержання координат вікна , що охоплює public Rectangle getBoundingBox (); •inside Перевірка, постійно перебує чи крапка всередині багатокутника 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); Якщо вам потрібно намалювати заповнений багатокутник (мал. 17), то для цього ви можете скористуватися методами, наведеними нижче: public abstract void fillPolygon ( int xPoints [], int yPoints [], int nPoints); public void fillPolygon (Polygon p); Перший з цих методів малює багатокутник, координати вершин якого специфіковані в масивах, другий - одержуючи об'єкт класу Polygon у вигляді параметра.
Мал. 16. Багатокутник, мальований методом fillPolygon 1.4. 4.4. Овали та колаДля малювання кіл та овалів ви можете скористуватися методом drawOval: public abstract void drawOval ( int x, int y, int width, int height); Параметри цього методи специфікують координати та розміри вікна, в яке вписується овал, що малюється (мал. 17).
Мал. 17. Малювання овала Метод fillOval передвизначений для малювання заповненого овала (мал. 18). Встановлення його параметрів аналогічно встановленню параметрів методу drawOval: public abstract void fillOval ( int x, int y, int width, int height);
Мал. 18. Малювання заповненого овала 1.4.4.5. СегментиМетод drawArc передвизначений для малювання порожнього сегменту (мал. 19). Дослідний зразок цього методу наведений нижче: public abstract void drawArc ( int x, int y, int width, int height, int startAngle, int arcAngle);
Мал. 19. Малювання порожнього сегменту Параметри x, y, width та height специфікують координати вікна, в яке вписаний сегмент. Параметри startAngle та arcAngle специфікуються у градусах. Вони визначають, відповідно, початковий кут та кут розвороту сегменту. Для того, щоб намалювати заповнений сегмент, ви можете скористуватися методом fillArc: public abstract void fillArc (int x, int y, int width, int height, int startAngle, int arcAngle); 1.4.4.6. Визначення зони обмеженняЯкщо для вікна аплету специфікувати зону обмеження, то малювання буде можливо тільки в межах цієї зони. Зона обмеження специфікується методом clipRect, дослідний зразок якого ми навели нижче: public abstract void clipRect ( int x, int y, int width, int height); Параметри x, y, width та height специфікують координати прямокутної зони обмеження. 1.4.4.7. Копіювання змісту прямокутної зониМетод copyArea дозволяє скопіювати зміст будь-якої прямокутної зони вікна аплету: public abstract void copyArea ( int x, int y, int width, int height, int dx, int dy); Параметри x, y, width та height специфікують координати зони прямокутної, що копіюється. Зона копіюється в іншу прямокутну зону такого ж розміру, причому параметри dx та dy визначають координати останньої. 1.4.5. Аплет DrawВ цьому розділі ми наведемо текст-джерела аплету Draw, в яких демонструється використання різних функцій малювання. На мал. 20 показане вікно цього аплету.
Мал. 20. Вікно аплету Draw В заголовку вікна ми вивели перелік всіх шрифтів, досяжних аплету, а також приклади оформлення рядка Test string з використанням цих шрифтів. Внизу вікна намальоване декілька геометричних фігур. 1.4.5.1. Текст-джерело аплету Draw/* Файл draw.java *\ import java.applet.; import java.awt.; public class draw extends Applet { Toolkit tk; String szFontList []; FontMetrics fm; int yStart=20; int yStep; String parm_TestString; public void init () { tk=Toolkit.getDefaultToolkit (); szFontList=tk.getFontList (); parm_TestString= getParameter ("TestString"); } public String getAppletInfo () { return "Name: draw"; } public void paint (Graphics g) { int yDraw; Dimension dimAppWndDimension=getSize (); 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); fm=g.getFontMetrics (); yStep=fm.getHeight (); for (int i=0; i < szFontList.length; i++) { g.setFont (new Font ("Helvetica", Font.PLAIN, 12)); g.drawString (szFontList [i], 10, yStart+yStep i); fm=g.getFontMetrics (); yStep=fm.getHeight (); g.setFont (new Font (szFontList [i], Font.PLAIN, 12)); g.drawString (parm_TestString, 100, yStart+yStep i); } yDraw=yStart+yStep szFontList.length +yStep; Polygon p=new Polygon (); p.addPoint (70, yDraw); p.addPoint (150, yDraw+30); p.addPoint (160, yDraw+80); p.addPoint (190, yDraw+60); p.addPoint (140, yDraw+30); p.addPoint (70, yDraw+39); g.drawPolygon (p); g.setColor (Color.red); g.drawRect (10, yDraw+85, 200, 100); g.setColor (Color.black); g.drawArc (10, yDraw+85, 200, 100,-50, 320); } public String [] [] getParameterInfo () { String [] [] info= { { "TestString", "String", "Test string" } }; return info; } } 1.4.5.2. Метод initПри присвоювання початкових значень аплету метод init витягає перелік досяжних шрифтів та вводить з клавіатури значення параметра TestString, що передається аплету в документі HTML. 1.4.5.2.1. Добування переліку шрифтів Процедура добування переліку досяжних шрифтів достатньо проста та виконується наступним чином: Toolkit tk; String szFontList []; ... tk=Toolkit.getDefaultToolkit (); szFontList=tk.getFontList (); Аплет викликає статичний метод getDefaultToolkit з класу Toolkit та після цього, користуючись отриманим звертанням, витягає перелік шрифтів, записуючи його в масив szFontList. 1.4.5.2.2. Для чого ще можна використовувати клас Toolkit? Клас Toolkit буде абстрактним надкласом для всіх реалізацій AWT. Підпорядковані від нього класи використовуються для зв'язку різних елементів конкретних реалізацій. Створюючи свої аплети, ви будете рідко вдаватися до послуг цього класу. Однак в ньому є декілька корисних методів, дослідний зразок яких ми перелічимо нижче: •getDefaultToolkit Одержання звертання на Toolkit public static Toolkit getDefaultToolkit (); •getColorModel Визначення поточного кольороподілу, вибраного в об'ємі відображення public abstract ColorModel getColorModel (); •getFontList Одержання переліку шрифтів, досяжних аплету public abstract String [] getFontList (); •getFontMetrics Одержання метрик специфікованого шрифту public abstract FontMetrics getFontMetrics (Font font); •getImage Одержання растрового відображення за іменем файлу public abstract Image getImage (String filename); •getImage Одержання растрового відображення за адресою URL public abstract Image getImage (URL url); •getScreenResolution Визначення резолюції відображувального екрану в крапках на дюйм public abstract int getScreenResolution (); •getScreenSize Розміри відображувального екрану у точках растра public abstract Dimension getScreenSize (); •prepareImage Готування растрового відображення для виведення public abstract boolean prepareImage ( Image image, int width, int height, ImageObserver observer); •sync Синхронізація стану Toolkit public abstract void sync (); Найбільш цікаві, з нашої точки зору, методи getFontList, getScreenResolution та getScreenSize, за допомогою яких аплет може, відповідно, отримати перелік шрифтів, визначити резолюцію та розмір відображувального екрану. Останні два параметри дозволяють сформувати зміст вікна аплету оптимальним чином виходячи з кількості інформації, що може в ньому розташуватися. 1.4.5.2.2. Збирання значення параметрів Досі наші аплети не одержували параметрів з документів HTML, в які ми їх вбудовували. Звичайно, всі константи, текстові рядки, адреси URL та інші дані можна закодувати безпосередньо в текст-джерелі аплету, однак, очевидно, це дуже незручно. Користуючись операторами <PARAM>, розташованими в документі HTML відразу після оператора <APPLET>, можна передати аплету довільну кількість параметрів, наприклад, у вигляді текстових рядків: <applet code=MyApplet.class id=MyApplet ... width=320 height=240 > <param name=ParamName1 value='Value 1'> <param name=ParamName2 value='Value 2'> <param name=ParamName3 value='Value 3'> <param name=ParamName4 value='Value 4'> ... </applet> Тут через параметр NAME оператора <PARAM> передається ім'я параметра аплету, а через параметр VALUE - значення відповідного параметра. Як параметр може отримати значення параметрів? Для одержання значення будь-якого параметра аплет повинен використовувати метод getParameter. У вигляді єдиного параметра цьому методу передається ім'я параметра аплету у вигляді рядка типу String, наприклад: parm_TestString=getParameter ("TestString"); Звичайно в аплеті також визначиться метод getParameterInfo, повертаючий дані про параметри. Ось текст-джерело цього методу для нашого аплету Draw: public String [] [] getParameterInfo () { String [] [] info= { { "TestString", "String", "Test string" } }; return info; } Метод getParameterInfo повертає масив масивів текстових рядків. Перший рядок вказує ім'я параметра, друга - тип даних, що через нього передаються, а третя - параметр, що приймає значення за замовчуванням. 1.4.5.3. Метод paintПерш за все метод paint визначає розміри вікна аплету, викликаючи для цього метод getSize: Dimension dimAppWndDimension=getSize (); Метод getSize повертає звертання на об'єкт класу Dimension, що запам'ятовує висоту та ширину об'єкту: •height Висота public int height; •width Ширина public int width; В класі Dimension передбачене три розробника та один метод: public Dimension (); public Dimension (Dimension d); public Dimension (int width, int height); •toString Одержання рядка, подаючого клас public String toString (); Після визначення розмірів вікна аплету метод paint витирає зміст всього вікна: g.clearRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Далі в контексті відображення встановлюється жовтий колір: g.setColor (Color.yellow); Цим кольором заповнюється внутрішня зона вікна аплету, для чого застосовується метод fillRect: g.fillRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Після цього метод paint встановлює в об'ємі відображення чорний колір та малює тонке чорне вікно навколо вікна аплету: g.setColor (Color.black); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); На наступному етапі ми одержуємо метрики поточного шрифту, вибраного в об'ємі відображення: fm=g.getFontMetrics (); Користуючись цими метриками, ми визначаємо висоту літер поточного фонту та записуємо її в поле yStep: yStep=fm.getHeight (); Після цього метод paint виконує цикл по всіх шрифтах, встановлених в системі: for (int i=0; i < szFontList.length; i++) { ... } Кількість досяжних фонтів дорівнює розміру масиву szFontList, який обчислюється як szFontList.length. Метод paint виписує в вікні аплету назву кожного шрифту, встановлюючи для цього шрифт Helvetica розміром 12 точок растра: g.setFont (new Font ("Helvetica", Font.PLAIN, 12)); g.drawString (szFontList [i], 10, yStart+yStep i); Зміщення кожного нового рядка з назвою шрифту обчислюється виходячи з висоти знаків встановленого шрифту: fm=g.getFontMetrics (); yStep=fm.getHeight (); Після назви шрифту метод paint малює у вікні аплету текстовий рядок parm_TestString, отриманий через параметр з ім'ям "TestString": g.setFont (new Font (szFontList [i], Font.PLAIN, 12)); g.drawString (parm_TestString, 100, yStart+yStep i); Перед тим як перейти до малювання геометричних фігур, метод paint запам’ятовує у полі yDraw координату останнього рядка назви шрифту, зробивши відступ висотою yStep: int yDraw; yDraw=yStart+ yStep szFontList.length+yStep; Перша фігура, яку малює наш аплет, це багатокутник. Ми створюємо багатокутник як об'єкт класу Polygon: Polygon p=new Polygon (); В цей об'єкт за допомогою методу addPoint додається декілька пунктів: p.addPoint (70, yDraw); p.addPoint (150, yDraw+30); p.addPoint (160, yDraw+80); p.addPoint (190, yDraw+60); p.addPoint (140, yDraw+30); p.addPoint (70, yDraw+39); Після розширення всіх пунктів метод paint малює багатокутник, викликаючи для цього метод drawPolygon: g.drawPolygon (p); Після цього ми встановлюємо в об'ємі відображення червоний колір та малюємо вікно: g.setColor (Color.red); g.drawRect (10, yDraw+85, 200, 100); Після цього метод paint вписує в це вікно сегмент кола: g.setColor (Color.black); g.drawArc (10, yDraw+85, 200, 100,-50, 320); 1.4.5.4. Документ HTML для аплету DrawДокумент HTML для аплету Draw не має жодних ознак. Він представлений нижче: /Файл draw.tmp.html\ <applet name='draw" code='draw" codebase='file:/e:/Sun/Articles/vol4/src/draw" width='250" height='350" align='Top" alt='If you had a java-enabled browser, you would see an applet here." > <param name='TestString" value='Test string'> <hr>If your browser recognized the applet tag, you would see an applet here. <hr> </applet> 1.4.5.5. Проект для аплету DrawПідготуйте файли проекту аплету Draw, скопіювавши їх з попереднього розділу в будь-яку директорію. Після цього запустіть майстера проектів та у відповідній діалоговій панелі вкажіть маршрут до цієї директорії. Таким чином, оскільки у директорії вже є файли, ви повинні ввімкнути перемикач Yes та після цього натиснути кнопку Next. Тут вам потрібно натиснути кнопку Add All in Directory. Як тільки ви це зробите, в переліку файлів Project Files з'явиться ім'я файлу draw.java, підготованого вами заздалегідь. На наступному кроці вам знову потрібно натиснути кнопку Next та трасувати, щоб ім'я головного класу аплету, що відображається в діалоговій панелі, було draw. Натиснувши клавішу Finish, ви можете завершити укладання проекту. Hаш аплет вводить з клавіатури з документа HTML один параметр з ім'ям TestString. Для розширення параметрів вам потрібно відкрити вікно керівника проектів, вибравши з меню Project рядок Show Project Manager. В цьому вікні проект draw повинен бути поточним. Виділити його курсором мишки у вікні Java WorkShop Project Manager та з меню Project цього вікна виберіть рядок Edit. Ви побачите блокнот Project Edit, за допомогою якого можна змінювати різні параметри проекту. Відкрийте в цьому блокноті сторінку Run. В поля Name та Value введіть, відповідно, ім'я параметра та параметр, що приймає значення за замовчуванням, а після цього натисніть клавішу Add. Доданий вами параметр з'явиться в переліку Parameters. Тепер вам залишилося тільки натиснути клавішу OK.
1.5. ПОДІЇ Від аплетів Java було б небагато користі, якби вони не вміли обробляти інформацію, що надходить від мишки та клавіатури. На щастя, таке оброблення передбачене та воно виконується достатньо просто. Коли користувач виконує операції з мишкою або клавіатурою у вікні аплету, виникають події, що передаються відповідним методам класу Applet. Перевизначаючи ці методи, ви можете організувати оброблення подій, що виникають від дій мишки або клавіатури. Якщо ви створювали прикладну програму для операційної системи Microsoft Windows, тут для вас немає нічого нового - згадати, як ви обробляли повідомлення WM_LBUTTONDOWN або WM_CHAR. Коли користувач виконував виконання з мишкою або клавіатурою у вікні прикладної програми, функція цього вікна одержувала відповідне повідомлення. Методи класу Applet, події , що обробляють від мишки та клавіатури, будуть аналогами маніпуляторів означених повідомлень. Помітимо, що аплети мають діло тільки з лівою клавішею мишки. В поточній версії Java ви не можете жодним чином викликати в аплеті праву або середню клавішу мишки. 1.5.1. Як обробляються подіїКоли виникає подія, контроль одержує метод handleEvent з класу Component. Клас Applet буде дочірнім по відношенню до класу Component. Дослідний зразок методу handleEvent ми навели нижче: public boolean handleEvent (Event evt); У вигляді параметра методу handleEvent передається об'єкт класу Event, що містить всі дані про подію. По змісту полів класу Event ви можете визначити координати курсора мишки в момент, коли користувач натиснув клавішу, відрізнити одинарний щиглик від бінарного й таким чином надалі. Нижче ми навели перелік полів класу Event, які ви можете проаналізувати:
Поле id (тип події) може містити наступні значення:
Якщо подія пов'язана з клавіатурою (тип події KEY_ACTION або KEY_ACTION_RELEASE), в поле key може постійно перебувати одне з наступних значень:
Можуть бути вказані наступні комбінації розрядів для поля модифікаторів modifiers:
Ваша прикладна програма може перевизначити метод handleEvent та обробляти події самостійно, однак є більш простий шлях. Оброблювач цього методу, що використовується за замовчанням, викликає декілька методів, що більш зручні в використанні, зокрема, при обробленні подій від мишки або клавіатури. 1.5.2. Події від мишкиВ цьому розділі ми розглянемо події, що виникають внаслідок того, що користувач виконує в вікні аплету операції з мишкою. Це такі операції, як риска і відпускання клавіші мишки, переміщення курсора мишки в вікні аплету з натицьнутою або відпущеною клавішею, переміщення курсора мишки в вікно аплету та вилучення цього курсора з вікна аплету. Всі перераховані нижче методи повинні повернути значення true, якщо оброблення події виконане успішно і подальше оброблення не вимагається. Якщо ж методи повернуть значення fasle, подія буде оброблена методом з базового класу, тобто для нього буде виконане оброблення, прийнята за замовчанням. Програмісти,що вже розробляли прикладні програми для операційної системи Microsoft Windows, можуть знайти тут аналогію з викликом функції DefWindowProc, що виконує оброблення повідомлень, прийнятих за замовчанням. 1.5.2.1. Натицькання на клавішу мишкиПеревизначивши метод 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"); 1.5. 2.2. Відпускання клавіші мишкиПри відпусканні клавіші мишки контроль одержує метод mouseUp: public boolean mouseUp (Event evt, int x, int y); Аналізуючи параметри x та y, ви можете визначити координати точки, в якій користувач відпустив клавішу мишки. 1.5.2.3. Переміщення курсора мишкиКоли користувач передає курсор мишки над вікном аплету, в процедурі вилучення відбувається виклик методу mouseMove: public boolean mouseMove (Event evt, int x, int y); Через змінні x та y передаються поточні координати курсора мишки. 1.5.2.4. Виконання операції Drag and DropОперація Drag and Drop виконується наступним чином: користувач натицькає клавішу мишки та, не відпускаючи її, переносить курсор мишки. При цьому відбувається виклик методу mouseDrag: public boolean mouseDrag (Event evt, int x, int y); Через змінні x та y передаються поточні координати курсора мишки. Метод mouseDrag викликається навіть в тому разі, якщо в процедурі вилучення курсор вийшов за границі вікна аплету. 1.5.2.5. Введення курсора мишки в ділянку вікна аплетуМетод mouseEnter одержує контроль, коли курсор мишки в процедурі вилучення по відображувальному екрану влучає в ділянку вікна аплету: public boolean mouseEnter (Event evt, int x, int y); Ви можете використовувати цей метод для виклику аплету, на який вказує курсор мишки. 1.5.2.6. Виведення курсора мишки з ділянки вікна аплетуМетод mouseExit викликається, коли курсор залишає вікно аплету: public boolean mouseExit (Event evt, int x, int y); Якщо користувач прибрав курсор з вікна аплету, який активувався методом mouseEnter, то метод mouseExit може перемкнути аплет в заблокований стан. 1.5.3. Аплет LineDrawВ аплеті LineDraw ми показали провідні принципи оброблення подій, що викликаються мишкою. Ви можете малювати у вікні аплету прямі лінії, причому біля кінців ліній відображаються їхні координати (мал. 21).
Мал. 21. Вікно аплету LineDraw з намальованими в ньому лініями Для того, щоб намалювати лінію в вікні аплету LineDraw, ви повинні встановити курсор в початкову точку, натицьнути клавішу мишки та після цього, не відпускаючи її, перемістити курсор в об'єктну точку. Після відпускання клавіші мишки координати рядка будуть збережені аплетом в масиві, після чого відбудеться перемалювання вікна аплету. По мірі того, як ви будете малювати лінії, аплет буде заповнювати масив з координатами ліній. Кожного разу, коли вікно аплету буде перемальовуватися, метод paint перемалює всі лінії ще раз, користуючись координатами, збереженими в масиві. Для того щоб стерти зміст вікна аплету, вам достатньо зробити бінарний щиглик в його вікні. При цьому з масиву координат рядків будуть вилучені всі елементи. 1.5.3.1. Текст-джерело аплету LineDrawТекст-джерело аплету LieDrnaw ви знайдете нижче: /Файл LieDrnaw.java\ import java.applet.; import java.awt.; import java.util.; public class LineDraw extends Applet { Dimension dmDown; Dimension dmUp; Dimension dmPrev; boolean bDrawing; Vector lines; public String getAppletInfo () { return "Name: LineDraw"; } public void init () { bDrawing=false; lines=new Vector (); } public void paint (Graphics g) { Dimension dimAppWndDimension=getSize (); setBackground (Color.yellow); g.setColor (Color.black); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); for (int i=0; i < lines.size (); i++) { Rectangle p= (Rectangle) lines.elementAt (i); g.drawLine (p.width, p.height, p.x, p.y); g.drawString ("<"+p.width +","+p.height+">", p.width, p.height); g.drawString ("<"+p.x+","+ p.y+ ">", p.x, p.y); } bDrawing=false; } public boolean mouseDown (Event evt, int x, int y) { if (evt.clickCount > 1) { lines.removeAllElements (); repaint (); return true; } dmDown=new Dimension (x, y); dmPrev=new Dimension (x, y); bDrawing=false; return true; } public boolean mouseUp (Event evt, int x, int y) { if (bDrawing) { dmUp=new Dimension (x, y); lines.addElement ( new Rectangle (dmDown.width, dmDown.height, x, y)); repaint (); bDrawing=false; } return true; } 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; } public boolean mouseMove (Event evt, int x, int y) { bDrawing=false; return true; } } Текст-джерело документа HTML, підготовленого системою JavaWorkshop, подане нижче: /Файл LineDraw.tmp.html\ <applet name="LineDraw" code="LineDraw" codebase= "file:/e:/Sun/Articles/vol5/src/LineDraw" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> </applet> 1.5.3.2. Опис текст-джерелаВ нашому аплеті ми будемо створювати об'єкт класу Vector, що буде масивом з динамічними енергозалежним розміром. Тут ми будемо запам'ятовувати координати мальованих рядків. Клас Vector має повне ім'я java. util. Vector, тому ми підключаємо відповідну бібліотеку класів: import java.util.; 1.5.3.2.1. Поля класу LineDraw В нашому класі ми визначили декілька полів, призначених для зберігання поточних координат ліній, що малюються: Dimension dmDown; Dimension dmUp; Dimension dmPrev; boolean bDrawing; Vector lines; В змінну dmDown класу Dimension записуються координати курсора на час натискання на клавішу мишки. Якщо користувач натиснув клавішу мишки для того щоб приступити до малювання лінії, це буде координатами початку лінії. Коли користувач відпускає клавішу мишки, координати записуються в змінну dmUp. В процедурі малювання лінії метод mouseDrag стирає раніше намальовану лінію та малює нову. Координати кінця старої лінії запам'ятовуються в змінній dmPrev. Змінна bDrawing типу boolean запам'ятовує сучасний стан аплету. Коли аплет постійно перебує у стані малювання лінії, в цю змінну записується значення true, а коли ні - значення false. Й, нарешті, змінна lines типу Vector буде динамічним масивом, в якому запам'ятовуються координати намальованих ліній.
1.5.3.2.2. Метод getAppletInfo Метод getAppletInfo вертає назву аплету та не має жодних ознак. 1.5.3.2.3. Метод init Метод init відкидає властивість малювання, записуючи в поле bDrawing значення false, а також створює новий динамічний масив у вигляді об'єкту класу Vector: public void init () { bDrawing=false; lines=new Vector (); } 1.5. 3.2. 4. Метод 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); g.drawString ("<"+p.width+"," +p.height+">", p.width, p.height); g.drawString ("<"+p.x+","+ p.y+ ">", p.x, p.y); } Для об'єктів класу Vector метод size повертає кількість елементів в масиві, чим ми скористувались для перевірки умови виходу з циклу. Щоб витягти елемент масиву по його кількості, ми застосували метод elementAt, передаючи йому через єдиний параметр кількість елемента , що витягається. Таким же чином як в масиві запам'ятовуються об'єкти класу Rectangle, перед присвоюванням початкових значень звертання p ми виконуємо явне перетворення типів. Координати кінців рядків малюються за допомогою вже знайомого вам методу drawString. Перед завершенням роботи метод paint відкидає властивість малювання, записуючи в поле bDrawing значення false: bDrawing=false; 1.5.3.2.4. Метод mouseDown На початку своєї роботи метод mouseDown визначає, чи був зроблений одинарний щиглик клавішею мишки, або бінарний. Якщо був зроблений бінарний щиглик мишкою, метод вилучає всіх елементи з масиву list, а після цього перемальовує вікно аплету, викликаючи метод repaint: lines.removeAllElements (); repaint (); Після перемалювання вікно аплету чиститься від ліній. Якщо ж був зроблений одинарний щиглик клавішею мишки, метод mouseDown зберігає поточні координати курсора в змінних dmDown та dmPrev, а після цього відкидає позначку малювання: dmDown=new Dimension (x, y); dmPrev=new Dimension (x, y); bDrawing=false; 1.5.3.2.5. Метод mouseUp Коли користувач відпускає клавішу мишки, викликається метод mouseUp. В його завдання входить збереження поточних координат курсора мишки у поле dmUp, а також додання нового елемента у масив lines: dmUp=new Dimension (x, y); lines.addElement ( new Rectangle (dmDown.width, dmDown.height, x, y)); repaint (); Після додання елемента у масив метод mouseUp викличе перемалювання вікна аплету, викликаючи для цього метод repaint. Помітимо, що у вигляді координат почала лінії ми записуємо в елемент масиву координати точки, де востаннє користувач натискав курсор мишки. У вигляді координат кінця рядка використовуються поточні координати курсора на час відпускання клавіші мишки. 1.5.3.2.6. Метод 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; 1.5.3.2.7. Метод mouseMove Метод mouseMove не робить нічого, за винятком того, що він вимикає режим малювання. Таким чином, просте переміщення курсора мишки над вікном аплету не призводить до малювання ліній.
1.6. КОМПОНЕНТИ
Практично кожна прикладна програма Windows, за винятком самих примітивних, має такі органи управління, як меню, кнопки, поля редагування буквено-цифрових даних, перемикачі з вимкненою і залежною фіксацією і переліки. Крім того, прикладна програма Windows може створювати діалогові панелі, що містять перераховані вище та інші органи управління. У вікні аплету ви також можете зарезервувати ресурси для деяких з перерахованих вище органів управління, а саме:
Найбільший і навряд чи приємний сюрприз для вас це те, що при призначенні перерахованих органів управління в вікні аплету ви не можете специфікувати для них точні координати і розміри. Призначення виконує система автоматичного керування появою Layout Manager, що розміщує органи управління по-своєму. Ви, однак, можете специфікувати декілька режимів призначення (послідовне, в елементах таблиці і таким чином далі), але не координати або розміри. Це зроблено для забезпечення незалежності прикладної програми Java від платформ, на яких вони виконуються. Органи управління створюються як об'єкти класів, підпорядкованих класу Component (мал. 22). Тому надалі ми будемо називати органи управління компонентами.
Мал. 22. Взаємодія класів органів управління в прикладній програмі Java Клас Button дозволяє створювати стандартні кнопки. Якщо вам потрібна нестандартна кнопка (наприклад, графічна кнопка), ви можете створити її на базі класу Canvas. Для генерування перемикачів з вимкненою або залежною фіксацією передвизначений клас CheckBox. За допомогою класу Label ви можете створювати в вікні аплету текстові рядки, наприклад, написи для інших компонентів. Ці рядки не редагуються користувачем. Клас List, як неважко здогадатися з назви, визначений для генерування переліків. За допомогою класу Scrollbar ви можете створювати смуги відображення інформації, що використовуються, зокрема, багаторядковими полями редагування тексту. Клас TextComponent є базисним для двох інших класів - TextField і TextArea. Перший з них визначений для генерування однорядкових редакторів тексту, другий - для генерування багаторядкових редакторів тексту. Для того, щоб зрозуміти, як компоненти розміщуються на поверхню вікна аплету системою Layout Manager, розглянемо іншу взаємодію класів Java, показану на мал. 22.
Мал. 22. Компоненти і контейнери Клас Component виступає у вигляді базового класу для класу Container. Об'єкти цього класу, якы ми будемо називати контейнерами, можуть містити об'єкти класів Component і Container. Таким чином, всередині контейнерів можуть постійно перебувати компоненти та інші контейнери. Клас Applet, таким же чином як інші класи, які походять від класу Container, буде контейнером. Це означає, що аплет може містити в собі компоненти (такі як органи управління) і контейнери. Відмітимо, що клас Applet успадковується від класу Container через клас Panel, в якому визначені методи системи Layout Manager. Налаштовуючи відповідним чином Layout Manager, ми можемо міняти стратегію призначення компонента всередині вікна аплету. В вікні аплету ви можете створити декілька об'єктів класу Panel (панелей), що розділять вікно на частині. Для кожної такої панелі можна встановити свою стратегію призначення компонента і контейнерів, що дозволяє виконувати достатньо складне компонування в вікні аплету. Тепер після отримання стислої інформації про контейнери і компоненти ми перейдемо до опису методів генерування окремих компонентів. 1.6. 1. КнопкиЯк ми вже говорили, стандартні кнопки створюються на базі класу Button. Цей клас дуже простий, тому ми наведемо повний його опис. 1.6. 1.1. Клас Button1.6. 1.1. 1. Розробники В класі Button визначені два розробника, перший з яких дозволяє створювати кнопку без напису, а другий - кнопку з написом. Зазвичай використовується другий розробник. public Button (); public Button (String label); 1.6. 1.1. 2. Метод addNotify Виклик методу createButton public void addNotify (); Збирання напису на кнопці 1.6. 1.1. 3. Метод getLabel public String getLabel (); Збирання рядка параметрів, що відбиває стан кнопки 1.6. 1.1. 4. Метод paramString protected String paramString (); Настроювання написи на кнопці 1.6. 1.1. 5. Метод setLabel public void setLabel (String label); З методів класу Button ви будете використовувати найчастіше два - getLabel і setLabel. Перший з них дозволяє отримати рядок напису на кнопці, а другий - встановити новий напис. Звичайно аплет створює в своєму вікні кнопки в процедурі свого присвоювання початкових значень при обробленні методу init, наприклад: Button btn1; ... public void init () { btn1=new Button ("Button 1)"; add (btn1); } Тут ми створили кнопку з написом Button 1. Після цього ми додали цю кнопку в контейнер, яким буде вікно аплету, за допомогою методу add. 1.6.2. Оброблення подій від кнопкиДля оброблення подій, створюваних кнопками та іншими компонентами, ви можете перевизначити метод 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. 1.6.3. ПеремикачіАплети Java можуть створювати у своєму вікні перемикачі двох типів: з вимкненою фіксацією та з залежною фіксацією. Перемикачі з вимкненою фіксацією мають прямокутну форму і, виходячи з назви, працюють вимкнено один від одного. Якщо такий перемикач постійно перебує у ввімкнутому стані, всередині відображення маленького квадрату з'являється галочка, якщо у вимкненому - галочка зникає. Звичайно перемикачі з вимкненою фіксацією використовуються для контролю незалежними один від одного режимами або параметрами. Перемикачі з залежною фіксацією мають круглу форму. В кожний момент часу може бути ввімкнутий тільки один такий перемикач з блоку перемикачів з фіксацією. Аплет може створювати декілька блоків перемикачів з залежною фіксацією. Перемикачі з залежною фіксацією використовуються для варіанту з декількох взаємовиключаючих можливостей, наприклад, для настроювання одного з декількох режимів. 1.6.3.1. Клас CheckboxПеремикачі з вимкненою та залежною фіксацією створюються на базі класу Checkbox. Наведемо дослідний зразок розробників та методів цього класу. 1.6.3.1.1. Розробники Генерування перемикача з вимкненою фіксацією без назви public Checkbox (); Генерування перемикача з вимкненою фіксацією та назвою public Checkbox (String label); Генерування перемикача з залежною фіксацією та назвою public Checkbox (String label, CheckboxGroup group, boolean state); 1.6.3.1.2. Метод addNotify. Виклик методу createCheckbox public void addNotify (); 1.6.3.1.3. Метод getCheckboxGroup Одержання блоку, до якого має відношення даний перемикач з залежною фіксацією public CheckboxGroup getCheckboxGroup (); 1.6.3.1.4. Метод getLabel Одержання назви перемикача public String getLabel (); 1.6.3.1.5. Метод getState Визначення сучасного стану перемикача public boolean getState (); 1.6.3.1.6. Метод paramString Одержання рядка параметрів protected String paramString (); 1.6.3.1.7. Метод setCheckboxGroup Настроювання блоку, до якого має відношення даний перемикач з залежною фіксацією public void setCheckboxGroup (CheckboxGroup g); 1.6.3.1.8. Метод setLabel Настроювання назви перемикача public void setLabel (String label); 1.6.3.1.9. setState Настроювання нового стану перемикача public void setState (boolean state); 1.6.3.2. Генерування перемикачів з вимкненою фіксацієюСтворити перемикач з вимкненою фіксацією не складніше, ніж створити кнопку: Checkbox rdbox1; ... public void init () { chbox1=new Checkbox ("Switch 1)"; add (chbox1); } В цьому фрагменті коду ми створюємо перемикач chbox1 з назвою Switch 1, а після цього за допомогою методу add додаємо його в контейнер, яким є вікно аплету. Для визначення сучасного стану перемикача ви можете використовувати метод getState. Якщо перемикач ввімкнутий, цей метод повертає значення true, а якщо вимкнен - значення false. 1.6.3.3. Генерування перемикачів з залежною фіксацієюДля кожної групи перемикачів з залежною фіксацією ви повинні створити об'єкт класу CheckboxGroup. 1.6.3.3.1. Розробник public CheckboxGroup (); 1.6.3.3.2. Методи Одержання звертання на перемикач, що постійно перебує в ввімкнутому стані: 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 в цьому прикладі передається назва перемикача, через другий - звертання на групу, а через третій - стан, в який повинен бути встановлений перемикач. Зі всіх перемикачів групи тільки один може постійно перебувати у ввімкнутому стані. 1.6.4. Переліки класу ChoiceНа базі класу Choice ви можете створити переліки типу Drop Down або, як їх ще називають, переліки 'що випадають'. Такий перелік має вигляд як літерно-цифрове поле висотою в один рядок, праворуч від якого розміщується кнопка (мал. 23).
Мал. 23. Перелік типу Drop Down, створений на базі класу Choice Якщо натиснути на цю кнопку, перелік відкриється та ви зможите вибрати варіант з його елементів (мал. 24).
Мал. 24. Розкритий перелік, створений на базі класу Choice В переліку класу Choice одночасно можна вибрати тільки один елемент. 1.6.4.1. Клас ChoiceНаведемо опис дослідного зразку розробника та методів класу Choice: 1.6.4.1.1. Розробник public Choice (); 1.6.4.1.2. Метод addItem Додавання елемента до переліку public void addItem (String item); 1.6.4.1.3. Метод addNotify Виклик методу createChoice public void addNotify (); 1.6.4.1.4. Метод countItems Визначення кількості елементів в переліку public int countItems (); 1.6.4.1.5. Метод getItem Збирання рядка переліку по номеру відповідного йому елемента переліку public String getItem (int index); 1.6.4.1.6. Метод getSelectedIndex Збирання кількості поточного вибраного елемента public int getSelectedIndex (); 1.6.4.1.7. Метод getSelectedItem Одержання рядка, що відповідає поточному вибраному елементу переліку public String getSelectedItem (); 1.6.4.1.8. Метод paramString Одержання рядка параметрів protected String paramString (); 1.6.4.1.9. Метод select Вибір в переліку елемента за специфікованим номером public void select (int pos); 1.6.4.1.10. Метод select Вибір в переліку елемента по специфікованому рядку public void select (String str); 1.6.4.2. Генерування переліківРозробник класу Choice не має параметрів. Генерування переліку з його допомогою не викличе у вас жодних перешкод: Choice chBackgroundColor; chBackgroundColor=new Choice (); Для наповнення переліку використовуйте метод addItem. У вигляді параметра йому необхідно передати текстовий рядок, який буде поєднаний з елементом, який додається до переліку: chBackgroundColor.addItem ("Yellow"); Далі перелік можна додати у вікно аплету як елемент за допомогою методу add: add (chBackgroundColor); Відмітимо, що перелік можна заповнювати до або після додавання до вікна аплету. Після наповнення переліку за замовчанням виділяється елемент, що був доданий в перелік першим. За допомогою методу select ви можете виділити будь-який елемент переліку за його номером або рядка, спарованого з елементом. Коли користувач вибирає новий рядок в переліку, виникає подія. Оброблювач цієї події, реалізований, наприклад, перевизначенням методу action, може отримати кількість вибраного рядка за допомогою методу getSelectedIndex. Приклад оброблення такої події ви знайдете в розділі "Прикладна Програма ChoiceList". Якщо вас цікавить не номер вибраного елементу, а рядок, спарований з вибраним елементом, скористуйтесь методом getSelectedItem. Та, нарешті, за допомогою методу getItem ви можете отримати текст рядка, спарованого з елементом, за номером елемента. 1.6.5. Переліки класу ListНа базі класу List ви можете зробити перелік іншого типу, що припускає варіант не тільки одного, але і декількох елементів. На відміну від переліку, створеного на базі класу Choice, перелік класу List може займати прямокутну ділянку, в якій розміщуються відразу декілька елементів. Цей перелік завжди постійно перебує в розкритому стані (мал. 25).
Мал. 25. Перелік класу List, всі елементи якого розміщуються у вікні переліку Якщо розміри вікна переліку класу List недостатні для того, щоб вмістити в себе всі елементи, в правій частині вікна переліку автоматично створюється лінійка відображення інформації, за допомогою якої можна перегорнути увесь перелік (мал. 26).
Мал. 26. Перелік класу List з лінійкою відображення інформації 1.6.5.1. Опис класу ListВ класі List визначено два розробника і досить багато різних методів. Нижче ми навели стислий опис класу List: 1.6.5.1.1. Розробники Розробник без параметрів public List (); Розробник, який дозволить вказати кількість рядків, що відображаються та ознака одночасного варіанту декількох елементів public List (int rows, boolean multipleSelections); 1.6.5.1.2. Методи
1.6.5.2. Генерування переліку класу ListПроцедура генерування переліку класу 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, наведеного в нашому посібнику. 1.6.5.3. Опрацювання подій від переліку класу ListНа відміну від переліку класу Choice, для варіанту рядка (або декількох рядків) з переліку класу List, користувач повинен зробити бінарний щиглик лівою клавішею мишки по виділеному елементу (або елементам, якщо виділено їх декілька). При цьому подію можна обробити перевизначеним методом action, як ми це робили для переліку класу Choice. Однак перелік класу List створює події не тільки при бінарному щигликові, але й при виділенні або відміни виділення елементів, зробленому користувачем одинарним щигликом клавіші мишки. Аплет може втручатися й обробляти такі події, перевизначивши метод handleEvent. 1.6.6. Літерно-цифрове поле класу LabelНа базі класу Label ви можете створити у вікні аплету однорядкове літерно-цифрове поле, що не піддається редагуванню. Головне призначення таких полів - підпис інших компонентів, таких, наприклад, як блоки перемикачів або переліки. 1.6.6.1. Клас LabelНижче ми навели стислий опис класу Label: 1.6.6.1.1. Поля Поля класу Label специфікують спосіб вирівнювання літерно-цифрового поля
1.6.6.1.2. Розробники Генерування літерно-цифрового поля без тексту public Label (); Генерування літерно-цифрового поля зі специфікованим текстом public Label (String label); Генерування літерно-цифрового поля зі специфікованим текстом та специфікованим вирівнюванням public Label (String label, int alignment); 1.6.6.1.3. Методи
1.6.6.2. Генерування поля класу LabelБуквено-цифрове поле класу Label створюється викликом відповідного розробника. Наприклад, нижче ми створили літерно-цифрове поле, вказавши рядок, який треба в нього записати: Label lbTextLabel; lbTextLabel= new Label ("Виберіть вирівнювання"); За допомогою методу add ви можете додати літерно-цифрове поле до вікна аплету: add (lbTextLabel); Метод setAlignment дозволяє у разі необхідності змінити вирівнювання тексту. Спосіб вирівнювання необхідно вказати через єдиний параметр методу: lbTextLabel.setAlignment (Label.LEFT); За допомогою методу setText ви зможите динамічні змінювати текст, розташований у полі класу Label. 1.6.7. Літерно-цифрове поле класу TextFieldДля редагування одного рядка тексту ви можете створити літерно-цифрове поле на базі класу TextField, яке легке в використанні. Клас TextField створений на базі іншого класу з ім'ям TextComponent, тому при роботі з літерно-цифровим полем класу TextField ви можете використовувати й методи класу TextComponent. 1.6.7.1. Клас TextFieldНаведемо стислий опис класу TextField: 1.6.7.1.1. Розробники Генерування поля без тексту public TextField (); Генерування поля без тексту з специфікованою шириною public TextField (int cols); Генерування поля та присвоювання початкових значень його текстом public TextField (String text); Генерування поля специфікованої ширини та присвоювання початкових значень його текстом public TextField (String text, int cols);
1.6.7.1.2. Методи
1.6.7.2. Генерування літерно-цифрового поля класу TextFieldПри генеруванні літерно-цифрового поля ви можете вибрати один з чотирьох розробників, відповідно для генерування поля без тексту й без відліку розміру, без тексту специфікованого розміру, для генерування поля з текстом та для генерування поля з текстом означеного розміру. Ось фрагмент коду, в якому створюється поле з текстом, що має ширину, достатню для розташування 35 літер: TextField txt; txt=new TextField ( "Введіть рядок тексту", 35); Створене поле додається в вікно аплету методом add. Більшість найкорисніших методів, необхідних для роботи з полем класу TextField, визначені в класі TextComponent, стислий опис якого ми навели нижче. 1.6.7.3. Клас TextComponentНижче наведені методи класу TextComponent.
За допомогою методу getText ви можете отримати увесь текст, що є у полі. Метод getSelectedText дозволяє отримати тільки ту частину тексту, що заздалегідь була виділена користувачем. Прикладна програма може виділити будь-який фрагмент тексту або весь текст за допомогою методів select та selectAll, відповідно. Для запису тексту в поле прикладна програма може скористуватися методом setText. Можливо, для вас буде цікавим метод setEditable, що дозволить перемикати літерно-цифрове поля з режиму, при якому редагування заблоковане, в режим з дозволеним редагуванням та зворотньо. 1.6.8. Багаторядкове літерно-цифрове поле класу TextAreaЯкщо вам потрібне поле для введення багаторядкових даних, зверніть увагу на клас TextArea. За його допомогою ви можете створити багаторядкове поле специфікованої ширини та висоти, постачене смугами відображення інформації. Клас TextArea створений на базі класу TextComponent, розглянутому нами раніше, тому для роботи з багаторядковими полями ви можете використовувати методи цього класу. Зокрема, вам досяжний метод, за допомогою якого можна одержувати з вікна редагування не весь текст, а тільки виділену користувачем ділянку. 1.6.8.1. Клас TextAreaСтислий опис класу TextArea ми навели нижче: 1.6.8.1.1. Розробники
1.6.8.1.2. Методи
1.6.8.2. Генерування поля TextAreaКоли ви створюєте багаторядкове літерно-цифрове поле редагування, то можете використовувати розробник, що припускає відлік розмірів поля в рядках та стовпчиках: TextArea txt; txt=new TextArea ("Введіть рядок тексту", 5, 35); Створене поле додається до вікна аплету методом add. Відзначимо, що в класі TextArea є методи для роботи з блоками тексту (вставка та заміна), а також методи, за допомогою яких можна визначити кількість рядків та стовпчиків у полі редагування. 1.6.9. Аплет FormDemoВ аплеті FormDemo ми покажемо методи роботи з компонентами, такими як перемикачі, кнопки, літерно-цифрові поля та переліки. Ми розмістили декілька таких компонентів у вікні цього аплету (мал. 27) таким чином, що вони об’єднають собою форму. В цій формі ви можете ввести ім'я та прізвище, вибрати один з трьох режимів роботи, а також колір.
Мал. 27. Вікно аплету FormDemo Перемикачі First та Second активують однорядкові літерно-цифрові поля редагування Enter your first name та Enter your second name. Після того як користувач натисне кнопку Ready, зміст активних полів, а також стан перемикачів Mode 1, Mode 2 та Mode 3 буде відображений в багаторядковому полі редагування. Це поле постійно перебує у низу вікна аплету. За допомогою переліку, розташованого праворуч від перемикача Mode 3, можна специфікувати колір фону багаторядкового поля. Колір встановлюється відразу після варіанту нового рядка з цього переліку. На жаль, при заміні розмірів вікна аплету розташовані в ньому компоненти змінюють своє визначення місця. Цей недолік ми вилучимо після того, як розповімо вам про систему Layout Manager, за допомогою якої ви можете керувати призначенням компоненти у вікні аплету. 1.6.9.1. Текст-джерело аплету FormDemoТекст-джерело аплету FormDemo ви знайдете в роздруку нижче. /*Файл FormDemo.java*\ import java.applet.Applet; import java.awt.; import java.util.; public class FormDemo extends Applet { Button btReady; Checkbox chbox1; Checkbox chbox2; CheckboxGroup grRadio; Checkbox rd1; Checkbox rd2; Checkbox rd3; Choice ch1; Label lbFirstName; Label lbSecondName; TextField txtFirstName; TextField txtSecondName; TextArea txta; public void init () { chbox1=new Checkbox ("First"); add (chbox1); lbFirstName= new Label ("Enter your first name: "); add (lbFirstName); txtFirstName=new TextField ("", 30); add (txtFirstName); chbox2=new Checkbox ("Second"); add (chbox2); lbSecondName= new Label ("Enter your second name: "); add (lbSecondName); txtSecondName=new TextField ("", 30); add (txtSecondName); grRadio=new CheckboxGroup (); rd1=new Checkbox ("Mode 1", grRadio, true); rd2=new Checkbox ("Mode 2", grRadio, false); rd3=new Checkbox ("Mode 3", grRadio, false); add (rd1); add (rd2); add (rd3); ch1=new Choice (); ch1. addItem ("White"); ch1. addItem ("Green"); ch1. addItem ("Yellow"); add (ch1); setBackground (Color.yellow); lbFirstName.setBackground (Color.yellow); lbSecondName.setBackground (Color.yellow); rd1.setBackground (Color.yellow); rd2.setBackground (Color.yellow); rd3.setBackground (Color.yellow); chbox1.setBackground (Color.yellow); chbox2.setBackground (Color.yellow); txta=new TextArea ("", 6, 45); add (txta); txta.setBackground (Color.white); btReady=new Button ("Ready"); add (btReady); } public String getAppletInfo () { return "Name: FormDemo"; } public void paint (Graphics g) { Dimension dimAppWndDimension= getSize (); g.setColor (Color.black); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } public boolean action (Event evt, Object obj) { Button btn; String str1, str2; if (evt.target instanceof Button) { if (evt.target.equals (btReady)) { btn=(Button) evt.target; str1=txtFirstName.getText (); str2=txtSecondName.getText (); if (chbox1.getState ()) txta.append (str1); if (chbox2.getState ()) txta.append (str2); if (rd1.getState ()) txta.append ("\nMode 1\n)"; if (rd2.getState ()) txta.append ("\nMode 2\n)"; if (rd3.getState ()) txta.append ("\nMode 3\n)"; } else { return false; } return true; } else if (evt.target instanceof Choice) { if (evt.target.equals (ch1)) { if (ch1.getSelectedIndex () == 0) txta.setBackground (Color.white); if (ch1.getSelectedIndex () == 1) txta.setBackground (Color.green); if (ch1.getSelectedIndex () == 2) txta.setBackground (Color.yellow); } } return false; } } В наступному роздруку ми навели текст-джерело документа HTML, створеного для нашого аплету системою Java WorkShop. /*Файл FormDemo.tmp.html*\ <applet name="FormDemo" code="FormDemo" codebase= "file:/e:/sun/articles/vol6/src/FormDemo" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> <hr>If your browser recognized the applet tag, you would see an applet here. <hr> </applet> 1.6.9.2. Опис текст-джерелаНаведемо стислий опис полів та методів, визначених в аплеті FormDemo. 1.6.9.2.1. Поля головного класу В головному класі нашого аплету ми визначили декілька полів. Поле btReady запам'ятовує звертання на кнопку з написом Ready: Button btReady; В полях chbox1 та chbox2 записані посилання на перемикачі з вимкненою фіксацією, що використовуються для виклику однорядкових літерно-цифрових полів: Checkbox chbox1; Checkbox chbox2; Поле grRadio запам'ятовує звертання на блок перемикачів з залежною фіксацією, яке визначає режими роботи Mode 1, Mode 2 та Mode 3: CheckboxGroup grRadio; Посилання на ці перемикачі постійно перебувають в наступних трьох полях: Checkbox rd1; Checkbox rd2; Checkbox rd3; В поле ch1 запам'ятовується звертання на перелік, призначений для вибору кольору: Choice ch1; Ліворуч від однорядкових полів редагування в нашому вікні існують підписи, реалізовані як об'єкти класу Label. Посилання на ці об'єкти постійно перебують в полях lbFirstName та lbSecondName: Label lbFirstName; Label lbSecondName; Посилання на однорядкові поля редагування записані в поля з іменами txtFirstName та txtSecondName: TextField txtFirstName; TextField txtSecondName; Та, нарешті, звертання на багаторядкове літерно-цифрове поле запам'ятовується у полі з ім'ям txta: TextArea txta; 1.6. 9.2. 2. Метод init Метод init займається генеруванням компонента та розширенням його у вікно аплета. Крім того, цей метод змінює колір фону вікна аплету і вікон компонента, що додаються. Перш за все метод init створює два перемикача з вимкненою фіксацією, два об'єкту класу Label та два однорядкових поля редагування тексту: chbox1=new Checkbox ("First"); add (chbox1); lbFirstName= new Label ("Enter your first name: "); add (lbFirstName); txtFirstName=new TextField ("", 30); add (txtFirstName); chbox2=new Checkbox ("Second"); add (chbox2); lbSecondName= new Label ("Enter your second name: "); add (lbSecondName); txtSecondName=new TextField ("", 30); add (txtSecondName); Поля створюються за допомогою розробників, а додаються до вікна аплету методом add. Згідно схеми визначення місця компонентів, встановленої за замовчанням, компоненти, що додаються, розміщуються зверху донизу та зліва направо. Для блоку перемикачів з залежною фіксацією ми створюємо об'єкт класу CheckboxGroup: grRadio=new CheckboxGroup (); Звертання на цей об'єкт після цього передається у вигляді другого параметра розробникам, що утворюють перемикачі: rd1=new Checkbox ("Mode 1", grRadio, true); rd2=new Checkbox ("Mode 2", grRadio, false); rd3=new Checkbox ("Mode 3", grRadio, false); Перемикачі додаються до вікна аплету за допомогою методу add: add (rd1); add (rd2); add (rd3); Перелік кольорів створюється як об'єкт класу Choice: ch1=new Choice (); Після генерування переліку ми додаємо до нього три елементи, викликаючи для цього метод addItem: ch1.addItem ("White"); ch1.addItem ("Green"); ch1.addItem ("Yellow"); Слідом за цим ми додаємо сформований перелік до вікна аплету: add (ch1); Для настроювання колір фону ми викликаємо метод setBackground без відліку об'єкту: setBackground (Color.yellow); В цьому випадку метод викликається для поточного об'єкту, тобто для нашого аплету. Щоб встановити колір фону в частинах вікна компонента, ми викликаємо метод setBackground для відповідних об'єктів, як це показано нижче: lbFirstName.setBackground (Color.yellow); lbSecondName.setBackground (Color.yellow); rd1.setBackground (Color.yellow); rd2.setBackground (Color.yellow); rd3.setBackground (Color.yellow); chbox1.setBackground (Color.yellow); chbox2.setBackground (Color.yellow); Багаторядкове літерно-цифрове поле створюється як об'єкт класу TextArea. В ньому 6 рядків та 45 стовпчиків: txta=new TextArea ("", 6, 45); add (txta); Первісний колір фону багаторядкового буквено-цифрового поля встановлюється тим же способом, який ми використовували для інших компонентів: txta.setBackground (Color.white); Цей колір надалі буде змінюватися маніпулятором подій, створюваних переліком кольорів. Та, нарешті, останнє що робить метод init перед тим як повернути контроль, - створює кнопку з написом Ready та додає її до вікна аплету: btReady=new Button ("Ready"); add (btReady); 1.6.9.2.3. Метод action В методі action ми визначили вилучили поля btn, str1 та str2: Button btn; String str1, str2; На початку своєї роботи метод action визначає, що компонента викликала подію. Для цього аналізується поле evt.target: if (evt.target instanceof Button) { ... return true; } else if (evt.target instanceof Choice) { ... return true; } return false; Наш метод action обробляє події, що викликаються об'єктами класів Button та Choice. Якщо подія викликана компонентою, стосовно до будь-якого іншого класу, метод вертає значення false. Цим він сигналізує, що оброблення події не виконувалося. В разі успішного оброблення події метод action вертає значення true. Якщо подія викликана кнопкою, наш метод action перевіряє, що саме. Оброблення виконується тільки в тому разі, якщо через поле evt.target передається звертання на кнопку btReady: if (evt.target.equals (btReady)) { ... } else { return false; } return true; В іншому випадку метод action повертає значення false, відмовляючись від оброблення події. Що робить оброблювач події, що створюється кнопкою? Перш за все, він зберігає звертання на кнопку в робочій змінній (просто для того щоб показати, як це робиться): btn=(Button) evt.target; Далі наш оброблювач події витягає текстові рядки з однорядкових літерно-цифрових полів, викликаючи для цього метод getText. Ці рядки записуються у робочі змінні str1 та str2: str1=txtFirstName.getText (); str2=txtSecondName.getText (); Після цього метод action перевіряє стан перемикачів з вимкненою фіксацією chbox1 та chbox2. Якщо вони ввімкнуті, зміст відповідних тимчасових змінних додається до багаторядкового літерно-цифрового поля txta: if (chbox1.getState ()) txta.append (str1); if (chbox2.getState ()) txta.append (str2); Для розширення ми викликаємо метод append. Аналогічним чином перевіряється стан перемикачів з залежною фіксацією: if (rd1.getState ()) txta.append ("\nMode 1\n)"; if (rd2.getState ()) txta.append ("\nMode 2\n)"; if (rd3.getState ()) txta.append ("\nMode 3\n)"; Якщо подія викликана переліком кольорів ch1, то метод action визначає, що рядок переліку став виділеним та встановлює в багаторядковому полі редагування відповідний колір фону. Для визначення виділеного рядка застосовується метод getSelectedIndex: if (evt.target.equals (ch1)) { if (ch1.getSelectedIndex () == 0) txta.setBackground (Color.white); if (ch1.getSelectedIndex () == 1) txta.setBackground (Color.green); if (ch1.getSelectedIndex () == 2) txta.setBackground (Color.yellow); } Роботу інших методів прикладної програми FormDemo ви зможете проаналізувати самостійно.
1.7. РОБОТА З СИСТЕМОЮ LAYOUT MANAGER
В попередньому розділі ми розповіли вам про те, як створювати компоненти та призначати їх ресурси у контейнері. Однак запропонований спосіб призначення компонентів у вікні контейнера навряд чи можна назвати зручним, так як заздалегідь тяжко передбачити, на яке місце потрапить той чи інший орган управління. На щастя, існують способи, що дозволять перевіряти призначення окремих компонента у вікні контейнера. Та хоча ці способи не дозволяють специфікувати конкретні координати і розміри органів управління, використувані схеми призначення компонента будуть вірогідно працювати на будь-якій апаратно-реалізованій платформі (не забудьте, що Java створювалася як засіб розробки прикладної програми, здатної виконуватися на будь-якій платформі). У чому складність генерування інтерфейсу користувача для багатоплатформових систем? В тому, що проектувальник прикладної програми ніколи не знає властивості пристрою відображення, встановленого у користувача. Він, зокрема, не може заздалегідь знати дозвіл диспетчера, розмір системного шрифту та інші властивості, необхідні для компонування діалогових панелей в термінах фізичних координат. Засоби інтерфейсу користувача AWT здатні динамічно змінювати розміри компонента, регулюючи їх "за місцем" в системі користувача. Внаслідок значно підвищується ймовірність того, що поява діалогової панелі, в якій вона постане перед користувачем, буде схожа на те, що чекав проектувальник. 1.7.1. Режими системи Layout ManagerПерш ніж ми розглянемо різні режими компонування системи Layout Manager, згадаємо, яка спадковість класу Applet (мал. 28).
Мал. 28. Спадковість класу Applet Клас Applet успадковується від класу Panel, який, в свою чергу, успадковується від класу Container та Component. Клас Container користується інтерфейсом LayoutManager, що дозволяє вибирати для контейнерів один з декількох режимів призначення компонента у вікні контейнера. Щодо класу Panel, то для нього за замовчанням вибирається режим призначення компонента з назвою Flow Layout. Зрозуміло, ви можете вибрати інший режим призначення, вказавши його явним чином. Нижче ми перерахували всі можливі режими системи Layout Manager:
Кожному режиму узгоджуться однойменний клас, методи та розробники, які дозволяють викликати різні схеми. Далі на прикладі конкретної прикладної програми ми розглянемо використання перерахованих вище режимів системи Layout Manager. 1.7.2. Режим FlowLayoutВ цьому режимі ми додавали компоненти в усіх прикладах аплетів, наведених раніше, таким чином як за замовчанням для аплетів використовується саме режим FlowLayout. 1.7.2.1. Клас FlowLayoutНижче ми навели стислий опис класу FlowLayout: 1.7.2.1.1. Поля Наступні три поля специфікують способи вирівнювання:
1.7.2.1.2. Розробники
Звичайно прикладна програма не викликає методи класу FlowLayout, встановлюючи варіанти компонування за допомогою розробників. Перший розробник класу FlowLayout не має параметрів. Він встановлює за замовчанням режим центрування компонента та зазор між компонентами по вертикалі й горизонталі, рівний 5 точкам растра. Саме цей режим й використовувався раніше в усіх наших аплетах, таким же чином як саме він застосовується за замовчанням об'єктами класу Panel, від якого успадковується клас Applet. За допомогою другого розробника ви можете вибрати режим призначення з специфікованим вирівнюванням компонента в вікні контейнера по горизонталі. У вигляді параметрів цьому розробнику необхідно передавати значення FlowLayout. LEFT, FlowLayout. RIGHT, або FlowLayout. CENTER. Зазор між компонентами буде при цьому рівний за замовчанням 5 точкам растра. Та, нарешті, третій розробник припускає роздільну індикацію режиму вирівнювання, а також зазорів між компонентами за вертикаллю й горизонталлю в точках растра. 1.7.2.1.3. Методи
1.7.3. Режим GridLayoutВ режимі GridLayout компоненти розміщуються в елементах таблиці, параметри якої можна специфікувати за допомогою розробників класу GridLayout. При призначенні компонента всередині елементів таблиці всі вони одержують однакові розміри. Якщо один з параметрів, які специфікують вимірність таблиці, дорівнює нулю, це означає, що відповідна колонка або рядок можуть містити будь-яку кількість елементів. Помітимо, що обидва параметра rows та cols не можуть бути рівним нулю одночасно. Наведемо опис розробників класу GridLayout. 1.7.3.1. РозробникиГенерування таблиці з специфікованою кількістю рядків та стовпчиків public GridLayout ( int rows, int cols); Генерування таблиці з специфікованою кількістю рядків та стовпчиків й зі специфікованим зазором між компонентами public GridLayout (int rows, int cols, int hgap, int vgap); 1.7.3.2. МетодиМетоди класу GridLayout використовуються рідко, тому ми їх тільки перелічимо. 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 (); 1.7.4. Режим BorderLayoutПри використанні режиму BorderLayout вікно контейнера відокремлюється на вікно та центральну частину. При призначенні компонента вказується напрямок від центру вікна, в якому слідує призначати ресурси компоненти. 1.7.4.1. Розробники класу BorderLayoutНижче наведений стислий опис розробників класу BorderLayout. public BorderLayout (); public BorderLayout (int hgap, int vgap); Ці розробники передвизначені для генерування схеми розміщення, без зазора між компонентами та із зазором специфікованої величини, відповідно. 1.7.4.2. Методи класу BorderLayoutПерелічимо також методи класу BorderLayout: 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 (); 1.7.4.3. Застосування класу BorderLayoutДодаючи компоненти до контейнера, ви повинні використовувати метод add з двома параметрами, перший з яких вказує напрямок розміщення, а другий - звертання на об'єкт , що додається: add ("North", btn1); add ("East", btn2); add ("West", btn3); add ("South", btn4); add ("Center", btn5); 1.7.5. Режим CardLayoutРежим CardLayout передвизначений для генерування комплекту діалогових панелей, що можна відображати за черзою в одному вікні прямокутної форми. Звичайно для контролю процедурою перебору діалогових панелей в режимі CardLayout використовуються окремі органи управління, розташовані в іншій панелі або навіть в іншому аплету на тій же самій сторінці сервера Web. Клас CardLayout містить два розробника та декілька методів. 1.7.5.1. Розробники класу CardLayoutРежим без зазорів public CardLayout (); Режим з зазорами по вертикалі та горизонталі між компонентами й вікном контейнера public CardLayout (int hgap, int vgap); 1.7. 5.2. Методи класу CardLayout
1.7.5.3. Використання режиму призначення 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". 1.7.6. Режим GridBagLayoutРежим GridBagLayout набагато складніший щойно описаного режиму GridLayout. Він дозволяє призначати ресурси компоненти різного розміру в таблиці, специфікуючи при цьому для окремих компонентів розміри відступу та кількість займаних комiрок. Зараз ми не будемо розглядати цей режим, таким чином як подібні результати можуть бути досягнуті іншими, менш складними способами. Наприклад, ви можете створити в контейнері декілька панелей, використавши всередині кожної свій метод призначення компоненти. Якщо ви створюєте аплети для розміщення в документах HTML, ніхто не змушує вас обмежуватися тільки одним аплетом для одного документа HTML - ви можете розмістити там довільну кількість аплетів, організувавши взаємодію з одного боку, між окремими аплетами, а з іншої - між аплетами та розширенннями сервера Web. В інтегрованій системі розробки прикладної програми Java WorkShop версії 2.0 існує вбудована система візуального конструювання інтерфейсу користувача, внаслідок роботи якої створюються текст-джерела класів. Призначення органів управління при цьому виконується діалоговими засобами. 1.7.7. Аплет FormLayoutУ вікні аплету FormLayout ми розмістили ті ж самі органи управління, що були використані в попередньому аплету FormDemo. Однак для ознаки способу розміщення компоненти ми виконали вирівнювання системи Layout Manager, вибравши режим GridLayout. Та хоча поки що поява нашої форми залишає бажати кращого, визначення місця окремих компонентів не змінюється при зміні розмірів вікна аплету. Проблема полягає в тому, що в режимі GridLayout не вдається керувати розмірами компоненти. Для вилучення цього недоліку слідує використовувати режим GridBagLayout. Оскільки цей режим доданий для використання без візуального конструювання, ми затримаємо подальше удосконалювання нашої форми до тих пір, доки не займемося вивченням відповідних засобів Java WorkShop. 1.7.7.1. Текст-джерело аплету FormLayoutТекст-джерело аплету FormLayout практично дублює текст-джерело аплету FormDemo, розглянутий в нашій попередній статті. Єдина відміна полягає в тому, що у методі init ми виконали налагодження системи Layout Manager, встановивши режим GridLayout: public void init () { setLayout (new GridLayout (4, 3)); ... } Тут для розміщення компоненти у вікні аплету створюється таблиця з чотирьох рядків та трьох стовпчиків. Повне текст-джерело аплету FormLayout ви знайдете в наступному роздруку. /*Файл FormLayout.java*\ import java.applet.Applet; import java.awt.; import java.util.; public class FormLayout extends Applet { Button btReady; Checkbox chbox1; Checkbox chbox2; CheckboxGroup grRadio; Checkbox rd1; Checkbox rd2; Checkbox rd3; Choice ch1; Label lbFirstName; Label lbSecondName; TextField txtFirstName; TextField txtSecondName; TextArea txta; public void init () { setLayout (new GridLayout (4, 3)); chbox1=new Checkbox ("First"); add (chbox1); lbFirstName=new Label ( "Enter your first name: "); add (lbFirstName); txtFirstName=new TextField ("", 30); add (txtFirstName); chbox2=new Checkbox ("Second"); add (chbox2); lbSecondName=new Label ( "Enter your second name: "); add (lbSecondName); txtSecondName=new TextField ("", 30); add (txtSecondName); grRadio=new CheckboxGroup (); rd1=new Checkbox ("Mode 1", grRadio, true); rd2=new Checkbox ("Mode 2", grRadio, false); rd3=new Checkbox ("Mode 3", grRadio, false); add (rd1); add (rd2); add (rd3); ch1=new Choice (); ch1.addItem ("White"); ch1.addItem ("Green"); ch1.addItem ("Yellow"); add (ch1); setBackground (Color.yellow); lbFirstName.setBackground ( Color.yellow); lbSecondName.setBackground ( Color.yellow); rd1.setBackground (Color.yellow); rd2.setBackground (Color.yellow); rd3.setBackground (Color.yellow); chbox1.setBackground (Color.yellow); chbox2.setBackground (Color.yellow); txta=new TextArea ("", 6, 45); add (txta); txta.setBackground (Color.white); btReady=new Button ("Ready"); add (btReady); } public String getAppletInfo () { return "Name: FormDemo"; } public void paint (Graphics g) { Dimension dimAppWndDimension= getSize (); g.setColor (Color.black); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } public boolean action (Event evt, Object obj) { Button btn; String str1, str2; if (evt.target instanceof Button) { if (evt.target.equals (btReady)) { btn=(Button) evt.target; str1=txtFirstName.getText (); str2=txtSecondName.getText (); if (chbox1.getState ()) txta.append (str1); if (chbox2.getState ()) txta.append (str2); if (rd1.getState ()) txta.append ("\nMode 1\n)"; if (rd2.getState ()) txta.append ("\nMode 2\n)"; if (rd3.getState ()) txta.append ("\nMode 3\n)"; } else { return false; } return true; } else if (evt.target instanceof Choice) { if (evt.target.equals (ch1)) { if (ch1.getSelectedIndex () == 0) txta.setBackground (Color.white); if (ch1.getSelectedIndex () == 1) txta.setBackground (Color.green); if (ch1.getSelectedIndex () == 2) txta.setBackground (Color.yellow); } } return false; } } Текст-джерело документа HTML, створений для нашого аплету системою Java WorkShop, представлене в наступному роздруку. /*Файл FormLayout.tmp.html*\ <applet name="FormLayout" code="FormLayout" codebase= "file:/e:/sun/articles/vol7/src/FormLayout" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> </applet> 1.8. РОБОТА З ПАНЕЛЯМИ
Панелі, створювані на базі класу Panel, будуть потужним засобом організації діалогового інтерфейсу. Оскільки клас Panel пішов від класу Container, панель може містити компоненти та інші панелі. Для кожної панелі можна визначити режим розміщення компонента, що дозволяє створювати достатньо складний інтерфейс користувача. У вікні аплету ви можете створити декілька панелей, що поділять його на частини. В свою чергу, простір, займаний панелями, також може бути розподілений з використанням одного з описаних вище режимів розміщення (мал. 29).
Мал. 29. Розміщення декількох панелей в вікні аплету Окремі панелі можуть містити в собі такі компоненти, як кнопки, перемикачі, переліки, літерно-цифрові поля та таке інше. 1.8.1. Генерування панелейПанель створюється дуже просто. Перш за все необхідно вибрати для вікна аплету схему розміщення компонента, відповідну необхідному визначенню місця панелей. Наприклад, для генерування у вікні аплету двох панелей, що поділять його за горизонталлю, слідує вибрати режим GridLayout: setLayout (new GridLayout (2, 1)); Панелі будуть розміщуватися в елементах таблиці, складатися з однієї колонки та двох рядків. Далі потрібне створити об'єкти класу Panel: Panel pTopPanel; pTopPanel=new Panel (); Panel pBottomPanel; pBottomPanel=new Panel (); Звертання на панель, що буде розміщуватися згори, записується до змінної pTopPanel, а на ту, що буде розміщуватися знизу - до змінної pBottomPanel. 1.8.2. Розширення панелейСтворивши панелі, ви можете додати їх у вікно аплету, викликавши метод 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. 1.8.3. Додавання компоненти до панеліДля додавання компоненти до панелі ви повинні вказати, для якої панелі викликається метод add, наприклад: Botton btn1; Botton btn2; btn1=new Button (); btn2=new Button (); pBottomPanel.add (btn1); pBottomPanel.add (btn2); 1.8.4. Малювання у вікні панеліЯк ви знаєте, для того щоб що-нибудь намалювати, необхідно спочатку отримати об'єм відображення. Методу 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. 1.8.5. Генерування нового класу на базі класу PanelЯкщо ваш аплет створює багато панелей, метод малювання в частинах вікна цих панелей, описаний вище, може призвести до ускладнення текст-джерела прикладної програми. Оскільки малювання в частинах вікна панелей виконується методом paint класу аплету, вам прийдеться одержувати об'єм відображення для кожної панелі. Набагато простіше створити декілька дочірніх класів від класу Panel, перевизначивши в кожному з них метод paint. В цьому випадку для кожної панелі ви можете створити свій метод paint, якому буде автоматично передаватися об'єм відображення, спарований з вікном відповідної панелі. В аплеті Options, який ми розглянемо нижче, використана саме така процедура роботи з панелями. 1.8.6. Аплет OptionsАплет Options демонструє процедури роботи з панелями, а також з різними режимами системи Layout Manager. У вікні аплету Options ми створили три панелі (мал. 30).
Мал. 30. Вікно аплету Options У верхній панелі відображається текстовий рядок First panel. Колір та шрифт цього рядка, а також колір фону можна специфікувати за допомогою другої панелі, розташованої в центрі вікна нашого аплету. Друга панель являє собою блокнот, на сторінках якого постійно перебувають переліки кольорів фону, тексту та шрифтів. За допомогою кнопок нижчої панелі ви можете перегортати сторінки цього блокноту. На мал. 31 та 32 ми показали сторінки, призначені для вибору кольору фону та кольору тексту, відповідно.
Мал. 31. Вибір кольору фону
Мал. 32. Вибір кольору тексту Натицькаючи кнопки Background Color, Foreground Color та Set Font, ви можете відображати потрібні вам сторінки блокноту. За допомогою кнопок Next та Prev можна перебирати сторінки блокноту в прямому або зворотному напрямку, відповідно. 1.8.6.1. Текст-джерело аплету OptionsТекст-джерело аплету Options подане в наступному роздруку. /*Файл Options.java*\ import java.applet.; import java.awt.; public class Options extends Applet { FirstPanel pPanel1; CardPanel pCard; ControlPanel pControl; public String getAppletInfo () { return "Name: Options"; } public void init () { setLayout (new GridLayout (3, 1)); pPanel1=new FirstPanel (); add (pPanel1); pCard=new CardPanel (pPanel1); add (pCard); pControl=new ControlPanel (pCard); add (pControl); pPanel1.setBackground (Color.yellow); pPanel1.setForeground (Color.black); repaint (); } } class FirstPanel extends Panel { String szFontName="TimesRoman"; public void paint (Graphics g) { Dimension dimAppWndDimension=getSize (); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); g.setFont (new Font (szFontName, Font. PLAIN, 24)); g.drawString ("First panel", 10, 50); super.paint (g); } } class CardPanel extends Panel { Panel pBgColor; Panel pFgColor; Panel pFont; Panel pControlled; Choice chBgColor; Choice chFgColor; Choice chFont; Label lbBgColor; Label lbFgColor; Label lbFont; public CardPanel (Panel pControlledPanel) { pControlled=pControlledPanel; setLayout (new CardLayout (5, 5)); pBgColor=new Panel (); pFgColor=new Panel (); pFont=new Panel (); add ("BgColor", pBgColor); add ("FgColor", pFgColor); add ("Font", pFont); chBgColor=new Choice (); chFgColor=new Choice (); chFont=new Choice (); chBgColor.add ("Yellow"); chBgColor.add ("Green"); chBgColor.add ("White"); chFgColor.add ("Black"); chFgColor.add ("Red"); chFgColor.add ("Green"); chFont.add ("TimesRoman"); chFont.add ("Helvetica"); chFont.add ("Courier"); lbBgColor=new Label ("Background color"); lbFgColor=new Label ("Foreground color"); lbFont=new Label ("Font"); pBgColor.add (lbBgColor); pBgColor.add (chBgColor); pFgColor.add (lbFgColor); pFgColor.add (chFgColor); pFont.add (lbFont); pFont.add (chFont); } public void paint (Graphics g) { Dimension dimAppWndDimension=getSize (); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); super.paint (g); } public boolean action (Event evt, Object obj) { Choice ch; if (evt.target instanceof Choice) { ch=(Choice) evt.target; if (evt.target.equals (chBgColor)) { if (ch.getSelectedIndex () == 0) pControlled.setBackground ( Color.yellow); else if (ch.getSelectedIndex () == 1) pControlled.setBackground ( Color.green); else if (ch.getSelectedIndex () == 2) pControlled.setBackground ( Color.white); } else if (evt.target.equals (chFgColor)) { if (ch.getSelectedIndex () == 0) pControlled.setForeground ( Color.black); else if (ch.getSelectedIndex () == 1) pControlled.setForeground ( Color.red); else if (ch.getSelectedIndex () == 2) pControlled.setForeground ( Color.green); } else if (evt.target.equals (chFont)) { if (ch.getSelectedIndex () == 0) ((FirstPanel) pControlled).szFontName ="TimesRoman"; else if (ch.getSelectedIndex () == 1) ((FirstPanel) pControlled).szFontName ="Helvetica"; else if (ch.getSelectedIndex () == 2) ((FirstPanel) pControlled).szFontName ="Courier"; } else { return false; } pControlled.repaint (); return true; } return false; } } class ControlPanel extends Panel { Button btNext; Button btPrev; Button btBgColor; Button btFgColor; Button btFont; Panel pCard; public ControlPanel (Panel pCardPanel) { pCard=pCardPanel; setLayout (new GridLayout (2.3)); btBgColor=new Button ("Background Color"); btFgColor=new Button ("Foreground Color"); btFont=new Button ("Set Font"); btNext=new Button ("Next"); btPrev=new Button ("Prev"); add (btBgColor); add (btFgColor); add (btFont); add (btNext); add (btPrev); } public boolean action (Event evt, Object obj) { if (evt.target instanceof Button) { if (evt.target.equals (btBgColor)) { ((CardLayout) pCard.getLayout ()).show ( pCard, "BgColor"); } else if (evt.target.equals (btFgColor)) { ((CardLayout) pCard. getLayout ()).show ( pCard, "FgColor"); } else if (evt.target.equals (btFont)) { ((CardLayout) pCard.getLayout ()).show ( pCard, "Font"); } else if (evt.target.equals (btNext)) { ((CardLayout) pCard.getLayout ()).next ( pCard); } else if (evt.target.equals (btPrev)) { ((CardLayout) pCard.getLayout ()). previous (pCard); } else { return false; } return true; } return false; } } Текст-джерело документа HTML, створеного для аплету Options системою Java WorkShop, подане в наступному роздруку. /*Файл Options.tmp.html*\ <applet name="Options" code="Options" codebase= "file:/E:/Sun/Articles/vol8/src/Options" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> <hr>If your browser recognized the applet tag, you would see an applet here. <hr> </applet> 1.8.6.2. Опис текст-джерела аплету OptionsОкрім базисного класу Options в нашому аплету створюється ще три класи для панелей з іменами FirstPanel, CardPanel та ControlPanel. Клас FirstPanel узгоджує саму верхню панель, в якій відображується рядок тексту First panel. Класи CardPanel та ControlPanel застосовуються для генерування панелей з переліками та кнопками. Ми будемо розглядати ці класи окремо. 1.8.6.2.1. Головний клас аплету Options В головному класі аплету Options ми визначили три поля з іменами pPanel1, pCard та pControl: FirstPanel pPanel1; CardPanel pCard; ControlPanel pControl; В них запам'ятовуються посилання на три класи, створених нами для трьох панелей. 1.8.6.2.2. Метод init Перш за все метод init встановлює для вікна аплету режим розміщення GridLayout: setLayout (new GridLayout (3, 1)); Вікно аплету відокремлюється на три горизонтальні зони, в яких ми будемо призначати ресурси панелі. Панелі створюються за допомогою оператора new як об'єкти відповідних класів, визначених у нашій прикладній програмі: pPanel1=new FirstPanel (); add (pPanel1); pCard=new CardPanel (pPanel1); add (pCard); pControl=new ControlPanel (pCard); add (pControl); Для розширення панелей у вікні аплету ми використовували метод add. Далі метод init встановлює початкові значення для кольору фону та тексту верхньої панелі: pPanel1.setBackground (Color.yellow); pPanel1.setForeground (Color.black); Зверніть увагу, що ми викликаємо методи setBackground та setForeground для об'єкту pPanel1. Після виконання всіх цих команд метод init перемальовуває вікно аплету, викликаючи метод repaint: repaint (); 1.8.6.2.3. Клас FirstPanel Ми створили клас FirstPanel на базі класу Panel, визначивши в ньому одне поле типу String та перевизначивши метод paint: class FirstPanel extends Panel { ... } Літерно-цифрове поле szFontName запам'ятовує назву шрифту, з використанням якого у вікні верхньої панелі відображається текстовий рядок: String szFontName="TimesRoman"; Метод paint визначає поточні розміри панелі та малює навколо неї прямокутне вікно: Dimension dimAppWndDimension=getSize (); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); Далі метод paint вибирає в об'єм відображення, спарований з панеллю, шрифт з назвою szFontName та малює текстовий рядок: g.setFont (new Font (szFontName, Font.PLAIN, 24)); g.drawString ("First panel", 10, 50); Помітимо, що відразу після запуску аплету вікно та рядок будуть намальовані з використанням чорного кольору, вибраного в об'єм відображення панелі за замовчанням. Надалі ви можете змінити цей колір за допомогою відповідної сторінки блокноту, реалізованої в другої панелі. Останнє виконання, що виконує метод paint першої панелі - виклик методу paint з батьківського класу: super.paint (g); Це призводить до перемалювання частини вікна аплету. 1.8.6.2.4. Клас CardPanel За допомогою класу CardPanel ми створили панель для блокноту, що містить три сторінки. Цей клас, таким же чином як й попередній, створений на базі класу Panel. В полях pBgColor, pFgColor та pFont запам'ятовуються посилання на панелі сторінок блокноту, які ми розмістимо всередині панелі класу CardPanel: Panel pBgColor; Panel pFgColor; Panel pFont; Крім того, в поле pControlled запам'ятовується звертання на верхню панель з текстовим рядком First Panel. Panel pControlled; Це поле буде проініційоване розробником класу CardPanel. В наступних трьох полях ми запам'ятовуємо посилання на переліки класу Choice, призначені, відповідно, для вибору кольору тексту, кольору фону та шрифту: Choice chBgColor; Choice chFgColor; Choice chFont; Три поля класу Label містять посилання на підписи до зазначених вище переліків: Label lbBgColor; Label lbFgColor; Label lbFont; При генеруванні об'єкту класу CardPanel ми передаємо розробнику звертання на верхню панель, параметрами якої потрібне керувати. Розробник записує це звертання в поле pControlled: public CardPanel (Panel pControlledPanel) { pControlled=pControlledPanel; ... } Після цього розробник встановлює режим настроювання CardLayout, залишаючи зазор по вертикалі та горизонталі, рівний п'ятим точкам растра: setLayout (new CardLayout (5, 5)); На наступному етапі ми створюємо три панелі для сторінок блокноту та додаємо їх до панелі CardPanel, специфікуючи імена: pBgColor=new Panel (); pFgColor=new Panel (); pFont=new Panel (); add ("BgColor", pBgColor); add ("FgColor", pFgColor); add ("Font", pFont); Тепер вам потрібне створити та заповнити три переліки, призначені для вибору кольору та шрифту. Ці переліки створюються як об'єкти класу Choice: chBgColor=new Choice (); chFgColor=new Choice (); chFont=new Choice (); Після генерування переліки наповнюються текстовими рядками. В кожний перелік ми додаємо по три рядки: chBgColor.add ("Yellow"); chBgColor.add ("Green"); chBgColor.add ("White"); chFgColor.add ("Black"); chFgColor.add ("Red"); chFgColor.add ("Green"); chFont.add ("TimesRoman"); chFont.add ("Helvetica"); chFont.add ("Courier"); Для того щоб постачити переліки підписами, ми створюємо три об'єкти класу Label: lbBgColor=new Label ("Background color"); lbFgColor=new Label ("Foreground color"); lbFont=new Label ("Font"); Ці об'єкти, а також переліки додаються на свої сторінки блокноту (тобто в свої панелі): pBgColor.add (lbBgColor); pBgColor.add (chBgColor); pFgColor.add (lbFgColor); pFgColor.add (chFgColor); pFont.add (lbFont); pFont.add (chFont); На цьому робота методу init завершується. 1.8.6.2.5. Метод action Метод action обробляє події, що виникають внаслідок вибору нових значень з переліків, розташованих на сторінках блокноту. Схема оброблення подій не має жодних властивостей. Спочатку метод action перевіряє, що подія викликана переліком класу Choice: if (evt.target instanceof Choice) { ... return true; } return false; } Події, спаровані з заміною колір фону, обробляються наступним чином: ch=(Choice) evt.target; if (evt.target.equals (chBgColor)) { if (ch.getSelectedIndex () == 0) pControlled.setBackground ( Color.yellow); else if (ch.getSelectedIndex () == 1) pControlled.setBackground ( Color.green); else if (ch.getSelectedIndex () == 2) pControlled.setBackground ( Color.white); } Тут метод setBackground викликається для об'єкту, звертання на який передається розробнику класу та записаний в поле pControlled. Це звертання на панель, розміщену у заголовку вікна нашого аплету. Аналогічним чином змінюється колір тексту та вікна для верхньої панелі: else if (evt.target.equals (chFgColor)) { if (ch.getSelectedIndex () == 0) pControlled.setForeground ( Color.black); else if (ch.getSelectedIndex () == 1) pControlled.setForeground ( Color.red); else if (ch.getSelectedIndex () == 2) pControlled.setForeground ( Color.green); } Для заміни шрифту ми встановлюємо нове значення змінної поля szFontName, визначеної в класі FirstPanel: else if (evt.target.equals (chFont)) { if (ch.getSelectedIndex () == 0) ((FirstPanel) pControlled).szFontName ="TimesRoman"; else if (ch.getSelectedIndex () == 1) ((FirstPanel) pControlled).szFontName ="Helvetica"; else if (ch.getSelectedIndex () == 2) ((FirstPanel) pControlled).szFontName ="Courier"; } Для того щоб адресуватися до поля szFontName, вам прийшлося виконати явне перетворення типу звертання pControlled. Останню операцію, що скоює метод action - це перемалювання вікна верхньої панелі, яке виконується за допомогою методу repaint: pControlled.repaint (); 1.8.6.2.6. Клас ControlPanel Клас ControlPanel створений для молодшої панелі з кнопками для керування. Наступні п'ять полів запам'ятовують посилання на кнопки для керування сторінками блокноту: Button btNext; Button btPrev; Button btBgColor; Button btFgColor; Button btFont; Поле pCard запам'ятовує звертання на панель блокноту: Panel pCard; Це звертання відформатується розробником класу. У завдання розробника класу ControlPanel входить запам'ятовувальний пристрій звертання на панель блокноту, встановлення режиму розміщення компоненти GridLayout, а також генерування та розширення в нижню панель кнопок для керування: public ControlPanel (Panel pCardPanel) { pCard=pCardPanel; setLayout (new GridLayout (2.3)); btBgColor=new Button ("Background Color"); btFgColor=new Button ("Foreground Color"); btFont=new Button ("Set Font"); btNext=new Button ("Next"); btPrev=new Button ("Prev"); add (btBgColor); add (btFgColor); add (btFont); add (btNext); add (btPrev); } Кнопки розміщуються в комiрках таблиці, що містить два рядки та три колонки. В цілому розробник класу ControlPanel не має жодних цікавих властивостей. 1.8.6.2.7. Метод action Метод action керує роботою блокноту, відображаючи його сторінки. Коли користувач натицькає на кнопки, що вибирають сторінки блокноту, метод action висує потрібну сторінку на передній план за допомогою методу show: if (evt.target.equals (btBgColor)) { ((CardLayout) pCard. getLayout ()).show ( pCard, "BgColor"); } else if (evt.target.equals (btFgColor)) { ((CardLayout) pCard.getLayout ()).show ( pCard, "FgColor"); } else if (evt.target.equals (btFont)) { ((CardLayout) pCard.getLayout ()).show ( pCard, "Font"); } У вигляді першого параметра цьому методу передається специфікатор формату панелі блокноту, а у вигляді другого - ім'я сторінки, яку необхідно відобразити. Циклічний перебір сторінок блокноту виконується за допомогою методів next та previous, відповідно: else if (evt.target.equals (btNext)) { ((CardLayout) pCard.getLayout ()).next ( pCard); } else if (evt.target.equals (btPrev)) { ((CardLayout) pCard.getLayout ()). previous (pCard); }
1.9. ВІКНА ТА ДІАЛОГОВІ ПАНЕЛІ
Досі ми малювали тільки у вікні аплету або у вікнах панелей, розташованих всередині вікна аплету. Однак є ще інша можливість - Java- програми, повноцінні та аплети, можуть створювати звичайні перекривані вікна, такі, наприклад, як вікно браузера. Ці вікна можуть мати меню (на відміну від вікон аплетів). Користувач може змінювати розмір таких вікон за допомогою мишки, переміщуючи рамку вікна. В складі бібліотеки класів AWT існує декілька класів, призначених для роботи з вікнами. Це клас Window, що пішов від класу Container, та його дочірні класи - Frame, Dialog і FileDialog (мал. 33).
Мал. 33. Ієрархія класів, призначених для генерування вікон Вікно, створене на базі класу Frame, більше всього схоже на головне вікно звичайної прикладної програми Windows. Воно може мати головне меню, для нього можна встановлювати форму курсора. Всередині такого вікна можна малювати. Таким чином як вікно класу Frame (таким же чином як та інші вікна AWT) пішли від класу Container, ви можете додавати в них різні компоненти та панелі, як ми це робили з вікнами аплетів та панелей. На базі класу Dialog створюються вікна діалогових панелей, дуже схожих на звичайні діалогові панелі Windows. Такі панелі не можуть мати меню та звичайно передвизначені для запиту будь-яких даних у користувача. Клас FileDialog передвизначений для генерування діалогових панелей діалогової панелі, за допомогою яких можна вибирати файли на локальних накопичувачах комп'ютера. Що до класу Window, то безпосередньо цей клас рідко застосовується для генерування вікон, оскільки класи Frame, Dialog та FileDialog більш зручні й забезпечують всі необхідні можливості. 1.9.1. Вікна класу FrameНижче ми навели стислий опис класу Frame. Таким чином як цей клас реалізує інтерфейс java.awt.MenuContainer, вікно класу Frame може містити меню. 1.9.1.1. ПоляЗа допомогою полів класу Frame ви можете специфікувати для свого вікна різні типи курсорів: 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; 1.9.1.2. РозробникиДля класу Frame визначене два розробника: Генерування вікна без заголовка public Frame (); Генерування вікна із заголовком public Frame (String title); 1.9.1.3. Методи
1.9.1.4. Застосування класу FrameДля того щоб створити своє вікно на базі класу 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); } 1.9.2. Меню в вікні класу FrameЯк ми вже говорили, вікно класу Frame може мати головне меню (Menu Bar) або, як ще говорять, лінійку меню. Головне меню створюється на базі класу MenuBar, стислий опис якого наведений нижче. 1.9.2.1. Розробникpublic MenuBar (); 1.9. 2.2. Методи
1.9.2.3. Робота з класом MenuBarДля складу головного меню вікна ви повинні створити об'єкт класу 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); 1.9.3. Клас MenuДля того щоб дати вам поняття про те, що можна робити з меню, наведемо стислий опис класу Menu: 1.9.3.1. РозробникиГенерування меню з специфікованою назвою public Menu (String label); Генерування меню з специфікованою назвою, що може залишатися на відображувальному екрані після того як користувач відпустив клавішу мишки public Menu (String label, boolean tearOff); 1.9.3.2. Методи
1.9.3.3. Робота з класом MenuМетод addSeparator використовується для додавання до меню розділового рядка. Аналогічний результат досягається й при додаванні до меню рядка "-": mnHelp.add ("-"); Помітимо, що ви можете просто додавати до меню рядка за їх назвою, користуючись методом add (String label), або додавати в меню елементи класу MenuItem, викликаючи метод add (MenuItem mi). 1.9.4. Клас MenuItemКлас MenuItem визначає виконання окремих елементів меню. Користуючись методами класу MenuItem ви можете блокувати або розблокувати окремо лінійку меню. Це потрібне робити, наприклад, якщо в даний момент функція, відповідна лінійці меню, вимкнена або не визначена. Ви також можете змінювати текстові рядки, відповідні елементам меню, що може згодитися для перевизначення їхнього встановлення. 1.9.4.1. Розробникpublic MenuItem (String label); 1.9. 4.2. Методи
1.9.5. Генерування діалогових панелейДіалогові панелі створюються на базі класу Dialog, стислий опис якого наведений нижче. 1.9.5.1. РозробникиГенерування діалогової панелі без заголовка public Dialog (Frame parent, boolean modal); Генерування діалогової панелі із заголовком public Dialog (Frame parent, String title, boolean modal); 1.9.5.2. Методи
1.9.5.3. Використання класу DialogДля того щоб створити свою діалогову панель, ви повинні визначити новий клас, успадкувавши його від класу 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, якщо, звичайно, це узгоджує логіку завдання вашої панелі. 1.9. 6. Прикладна програма MenuAppАвтономна прикладна програма MenuApp, працююча під контролем інтерпретатора Java, демонструє способи генерування меню. В його вікні (мал. 34) існує панель з меню File та Help.
Мал. 34. Головне вікно автономної прикладної програми MenuApp
В меню File ми додали рядки New та Exit, а також розділовий знак у вигляді горизонтальної лінії (мал. 35).
Мал. 35. Меню File
Меню Help (мал. 36) містить рядки Content і About. Між ними також існує розділова лінія.
Мал. 36. Меню Help
Якщо вибрати будь-який рядок, окрім рядка Exit з меню File, на відображувальному екрані з'явиться діалогова панель з назвою вибраного рядка та кнопкою OK (мал. 37).
Мал. 37. Діалогова панель, що з'являється при виборі рядка New з меню File Вибір рядка Exit з меню File призводить до завершення роботи прикладна програма MenuApp. 1.9.6.1. Текст-джерело прикладної програми MenuAppТекст-джерело прикладної програми MenuApp наведене в наступному роздруку. /*Файл MenuApp. java*\ import java.awt.; public class MenuApp { public static void main (String args []) { MainFrameWnd frame= new MainFrameWnd ("MenuApp"); frame.setSize ( frame.getInsets ().left+ frame.getInsets ().right+320, frame.getInsets ().top+ frame.getInsets ().bottom+240); frame.show (); } } class MainFrameWnd extends Frame { MenuBar mbMainMenuBar; Menu mnFile; Menu mnHelp; public MainFrameWnd (String sTitle) { super (sTitle); setSize (400, 200); setBackground (Color. yellow); setForeground (Color. black); setLayout (new FlowLayout ()); mbMainMenuBar=new MenuBar (); mnFile=new Menu ("File"); mnFile.add ("New"); mnFile.add ("-)"; mnFile.add ("Exit"); mnHelp=new Menu ("Help"); mnHelp.add ("Content"); mnHelp.add ("-)"; mnHelp.add ("About"); mbMainMenuBar.add (mnFile); mbMainMenuBar.add (mnHelp); setMenuBar (mbMainMenuBar); } public void paint (Graphics g) { g.setFont (new Font ( "Helvetica", Font. PLAIN, 12)); g.drawString ("Frame window", 10, 70); super.paint (g); } public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) { setVisible (false); System.exit (0); return true; } else return super.handleEvent (evt); } public boolean action (Event evt, Object obj) { MenuItem mnItem; if (evt.target instanceof MenuItem) { mnItem=(MenuItem) evt.target; 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")) { MessageBox mbox; mbox=new MessageBox ( "Item Content selected", this, "Dialog from Frame", true); mbox.show (); } 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; } } class MessageBox extends Dialog { Label lbMsg; Button btnOK; public MessageBox (String sMsg, Frame parent, String sTitle, boolean modal) { super (parent, sTitle, modal); resize (200, 100); setLayout (new GridLayout (2, 1)); lbMsg=new Label (sMsg, Label.CENTER); add (lbMsg); btnOK=new Button ("OK"); add (btnOK); } public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) { dispose (); return true; } else return super.handleEvent (evt); } public boolean action (Event evt, Object obj) { Button btn; if (evt.target instanceof Button) { btn=(Button) evt.target; if (evt.target.equals (btnOK)) { dispose (); } else return false; return true; } return false; } } 1.9.6.2. Опис текст-джерела прикладної програми MenuAppЯк ми вже говорили, прикладна програма MenuApp працює автономно. Тому ми імпортуємо тільки клас java.awt., необхідний для роботи з вікнами: import java.awt.; В нашій прикладній програмі визначене три класу - MenuApp, MainFrameWnd і MessageBox. 1.9.6.2.1. Клас MenuApp В головному класі прикладної програми MenuApp ми визначили тільки один метод main. Цей метод одержує контроль при запуску прикладної програми. Першою справою метод main створює об'єкт класу MainFrameWnd, визначеного в нашій прикладній програмі: MainFrameWnd frame= new MainFrameWnd ("MenuApp"); Цей клас, створений на базі класу Frame, встановлює виконання головного вікна нашої прикладної програми. На другому кроці метод init налаштовує розміри головного вікна з урахуванням розмірів зовнішнього вікна та заголовка вікна: frame.setSize (frame.getInsets ().left+ frame.getInsets ().right+320, frame.getInsets ().top+ frame.getInsets ().bottom+240); Поля left та right об'єкту класу Insets, звертання на який вертає метод getInsets, містять ширину лівої та правої частини рамки вікна, відповідно. Поле top містить висоту голови рамки вікна з урахуванням заголовка, а поле bottom - висоту дна рамки вікна. Для відображення рамки вікна ми викликаємо метод show, як це показане нижче: frame.show (); 1.9.6.2.2. Клас MainFrameWnd Клас MainFrameWnd створений на базі класу Frame: class MainFrameWnd extends Frame { ... } В ньому ми визначили три поля, розробник, методи paint, handleEvent та action. Поле mbMainMenuBar призначене для зберігання звертання на головне меню прикладної програми, створюване як об'єкт класу MenuBar: MenuBar mbMainMenuBar; Поля mnFile та mnHelp запам'ятовують посилання на меню File та Help, відповідно: Menu mnFile; Menu mnHelp; Дані меню створюються на базі класу Menu. У вигляді єдиного параметра розробнику класу MainFrameWnd передається заголовок створюваного вікна. В першому виконуваному рядку наш розробник викликає розробник з базового класу, передаючи йому заголовок вікна через параметр: public MainFrameWnd (String sTitle) { super (sTitle); ... } Далі розробник визначає розміри вікна, викликаючи для нього метод setSize: setSize (400, 200); Після цього ми встановлюємо для нашого вікна жовтий колір фону та чорний колір відображення: setBackground (Color.yellow); setForeground (Color.black); За замовчанням для вікон класу Frame встановлюється режим додавання компоненти BorderLayout. Ми змінюємо цей режим на FlowLayout, викликаючи метод setLayout: setLayout (new FlowLayout ()); Далі розробник приступає до складу головного меню вікна. Це меню створюється як об'єкт класу MenuBar: mbMainMenuBar=new MenuBar (); Після цього ми створюємо й наповнюємо меню "File": mnFile=new Menu ("File"); mnFile.add ("New"); mnFile.add ("-)"; mnFile. dd ("Exit"); Це меню створюється на базі класу Menu. Зверніть увагу, що між рядками New і File розміщений ділильний пристрій. Аналогічним чином ми додаємо в головне меню інше меню - "Help": mnHelp=new Menu ("Help"); mnHelp.add ("Content"); mnHelp.add ("-)"; mnHelp.add ("About"); Після свого кінцевого складу меню "File" та "Help" додаються в головне меню вікна mbMainMenuBar: mbMainMenuBar.add (mnFile); mbMainMenuBar.add (mnHelp); Та, нарешті, коли головне меню буде сформоване, воно підключається до вікна викликом методу setMenuBar, як це показане нижче: setMenuBar (mbMainMenuBar); 1.9.6.2.3. Метод paint Метод paint одержує у вигляді параметра звертання на об'єм відображення, придатного для малювання в нашому вікні. Користуючись цим об'ємом, ми встановлюємо шрифт тексту та малюємо текстовий рядок. Після цього ми викликаємо метод paint з базового класу Frame, на основі якого створений наш клас MainFrameWnd: public void paint (Graphics g) { g.setFont (new Font ( "Helvetica", Font.PLAIN, 12)); g.drawString ("Frame window", 10, 70); super.paint (g); } 1.9.6.2.4. Метод handleEvent Для того щоб визначити реакцію вікна на спробу користувача закрити вікно за допомогою органів управління, розташованих в заголовку вікна, або іншим способом, ми перевизначили метод handleEvent. При одержанні коду події Event. WINDOW_DESTROY (вилучення вікна) ми переховуємо вікно, викликаючи метод setVisible з параметром false. Після цього за допомогою статичного методу exit класу System ми завершуємо роботу інтерпретатора: public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) { setVisible (false); System.exit (0); return true; } else return super.handleEvent (evt); } 1.9.6.2.5. Метод action Цей метод обробляє події, що виникають при виборі рядок з меню. На початку своєї роботи метод action перевіряє, дійсно чи подія викликана меню: MenuItem mnItem; if (evt.target instanceof MenuItem) { ... } 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. В тому разі коли користувач вибирає будь-який інший рядок з меню, метод action створює діалогову панель на базі визначеного нами класу MessageBox. В цій діалоговій панелі відображується назва вибраної лінійка меню. Помітимо, що відразу після генерування розробником діалогова панель не з'являється на відображувальному екрані. Ми відображаємо її, викликаючи метод show. 1.9.6.2.6. Клас MessageBox Для відображення назв вибраних рядків меню ми створюємо діалогову панель, визначивши свій клас MessageBox на базі класу Dialog, як це показане нижче: class MessageBox extends Dialog { ... } В класі MessageBox є два поля, розробник, методи handleEvent та action. Всередині діалогової панелі ми розмістили літерно-цифрове поле класу Label, призначене для відображення повідомлення, та кнопку з написом OK, за допомогою якої можна завершити роботу діалогової панелі. Звертання на літерно-цифрове поле запам'ятовується в поле lbMsg, на кнопку - в поле btnOK. Наш розробник створює діалогову панель з специфікованим повідомленням всередині неї. Звертання на рядок повідомлення передається розробнику через перший параметр. Інші параметри використовуються розробником базового класу 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); 1.9.6.2.7. Метод handleEvent класу MessageBox Коли користувач намагається закрити вікно діалогової панелі, наприклад, зробивши бінарний щиглик лівим клавішею мишки по системному меню або поодинокий щиглик по кнопці вилучення вікна, виникає подія Event. WINDOW_DESTROY. Ми її обробляємо наступним чином: if (evt.id == Event.WINDOW_DESTROY) { dispose (); return true; } else return super.handleEvent (evt); Викликаючи метод dispose, ми вилучаємо вікно діалогової панелі та звільняємо всі спаровані з ним ресурси. 1.9.6.2.8. Метод action класу MessageBox Якщо користувач натискає кнопку OK, розташовану в вікні діалогової панелі, метод action викликає для панелі метод dispose, вилучаючи цю панель з відображувального екрану й з оперативної пам'яті: if (evt.target.equals (btnOK)) { dispose (); }
1.10. БАГАТОПОТОКОВІСТЬ
Напевно, сьогодні вже немає необхідності пояснювати, що таке багатопотоковість. Всі сучасні операційні системи, такі як Windows 95, Windows NT, OS/2 або UNIX здатні працювати в багатопроцесному режимі, підвищуючи загальне виконання системи за рахунок ефективного розпаралелювання виконованих трафіків. Доки один потік постійно перебує в змозі чекання, наприклад, завершення операції обміну даними з повільним зовнішнім пристроїм, інший може продовжувати виконувати свою роботу. Користувачі вже давно призвичаїлися виконувати паралельно декілька прикладних програм для того, щоб робити декілька дій відразу. Доки одна з них займається, наприклад, друком документа на принтері або одержанні електронної пошти з мережі Internet, інша може перераховувати електронну таблицю або виконувати іншу корисну роботу. При цьому сама по собі виконована прикладна програма може працювати в рамках одного потоку (трафіку) - операційна система сама дбає про призначення часу між всіма запущеними прикладними програмами. Створюючи прикладну програму для операційної системи Windows на мовах програмування С або С++, ви могли розв'язувати численні завдання, такі як відображення анімації або робота в мережі, й без використання багатопотоковості. Наприклад, для анімації можна було обробляти повідомлення відповідним чином налаштованого таймера. Прикладній програмі Java така процедура недосяжна, оскільки в цьому середовищі не передбачене засобів періодичного виклику будь-яких процедур. Тому для розв'язування численних завдань вам просто не обійтися без багатопотоковості. 1.10.1. Процедури, трафіки та пріоритетиПерш ніж приступити до діалога про багатопотоковість, слідує уточнити деякі терміни. Звичайно в будь-якій багатопроцесній операційній системі виділяють такі об'єкти, як процедури та трафіки. Між ними існує велика різниця, яку слідує чітко собі представляти. 1.10.1.1. ПроцедураПроцедура (process) - це об'єкт, що створюється операційною системою, коли користувач виконує прикладну програму. Процедурі виділяється окремий адресний простір, причому цей простір фізічно вимкнено для інших процедур. Процедура може працювати з файлами або з каналами локальної або глобальної мережі. Коли ви запускаєте текстовий процесор або програму обчислювача, ви створюєте нову процедуру. 1.10.1.2. ТрафікДля кожної процедури операційна система створює один головний трафік (thread - потік), що буде потоком виконованих по черзі команд центрального процесору. У разі необхідності головний трафік може створювати інші трафіки, користуючись для цього програмним інтерфейсом операційної системи. Всі трафіки, створені процедурою, виконуються в адресному просторі цієї процедури й мають доступ до ресурсів процедури. Однак трафік однієї процедури не має жодного доступу до ресурсів трафіку іншої процедури, оскільки вони працюють в різних адресних просторі. У разі необхідності організації взаємодії між процедурами або трафіками, що належать різним процедурам, слідує користуватися системними засобами, спеціально призначеними для цього. 1.10.1.3. Пріоритети трафіків у прикладній програмі JavaЯкщо процедура створила декілька трафіків, то всі вони виконуються паралельно, причому час центрального процесору (або декількох центральних процесорів в багатопроцесорових системах) розподіляється між цими трафіками. Призначенням часу центрального процесору займається спеціальний модуль операційної системи - планувальник. Планувальник по черзі передає контроль окремим трафікам, таким чином що навіть в однопроцесорній системі створюється повна омана паралельної роботи виконаних трафіків. Призначення часу виконується за внутрішнім перериванням системного годинника. Тому кожному потоку дається визначене вирівнювання рядка часу, в потоці якого він постійно перебує в активному стані. Помітимо, що призначення часу виконується для трафіків, а не для процедур. Трафіки, створені різними процедурами, конкурують між собою за одержання процесорного часу. Прикладна Java-програма може вказувати три значення для пріоритетів трафіків. Це NORM_PRIORITY, MAX_PRIORITY та MIN_PRIORITY. За замовчанням знов створений трафік має нормальну черговість NORM_PRIORITY. Якщо інші трафіки в системі мають ту же саму черговість, то всі трафіки користуються цепеушним часом на рівних правах. У разі необхідності ви можете підвищити або понизити черговість окремих трафіків, визначивши для них значення черговості, відповідно, MAX_PRIORITY або MIN_PRIORITY. Трафіки з підвищеною черговістю виконуються насамперед, а з пониженою - тільки за відсутності готових до виконання трафіків, що існують, нормальна або підвищена черговість. 1.10.2. Реалізація багатопотоковості в JavaДля реалізації багатопотоковості ми повинні скористуватися класом java.lang.Thread. В цьому класі визначені всі методи, необхідні для генерування трафіків, управління їхнім станом та синхронізація. Як користуватися класом Thread? Є дві можливості.
Другий спосіб особливо зручний в тих випадках, коли ваш клас повинен бути успадкований від якого-або іншого класу (наприклад, від класу Applet) та при цьому вам потрібна багатопотоковість. Оскільки у мові програмування Java немає багаторазової спадковості, неможливо створити клас, для якого у вигляді батьківського будуть виступати класи Applet та Thread. В цьому випадку реалізація інтерфейсу Runnable буде єдиним способом розв'язування завдання. 1.10.2.1. Методи класу ThreadВ класі Thread визначені три поля, декілька розробників та велика кількість методів, призначених для роботи з трафіками. Нижче ми навели стислий опис полів, розробників та методів. За допомогою розробників ви можете створювати трафіки різними способами, вказуючи у разі необхідності для них ім'я та групу. Ім'я передвизначене для розпізнавання потоку й буде довільною ознакою. Що до груп, то вони передвизначені для організації безпеки трафіків одне від одного у межах однієї прикладної програми. Методи класу Thread надають всіх необхідні можливості для керування потоком, в тому числі для їхньої синхронізації. 1.10.2.1.1. Поля Три статичних поля передвизначені для встановлення пріоритетів трафікам.
1.10.2.1.2. Розробники Генерування нового об'єкту 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); 1.10. 2.1. 3. Методи
1.10.2.3. Генерування дочірнього класу на базі класу 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, що містить аплет. 1.10.2.4. Реалізація інтерфейсу 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. 1.10.3. Застосування багатопотоковості для анімаціїОдне з найбільш розповсюджених застосуваннь аплетів - це генерування анімаційних ефектів типу біжучого рядка, миготливих вогнів або аналогічних, привертаючих увагу користувача. Для того щоб досягнути такого ефекту, потрібний механізм, що дозволить виконувати перемалювання всього вікна аплету або його частини періодично з специфікованим проміжком часу. Робота аплетів, таким же чином як й звичайної прикладної програми операційної системи Windows, започаткованої на обробленні подій. Для класичної прикладної програми Windows подія - це прихід повідомлення до функції вікна. Базисний клас аплету обробляє події, перевизначаючи ті чи інші методи базового класу Applet. Проблема з періодичним коректуванням вікна аплету виникає з-за того, що в мові Java не передбачено жодного механізму для генерування генератора подій, здатного викликати будь-який метод класу аплету з специфікованим проміжком часу. Ви не можете вчинити таким чином, як чинили в цьому випадку, розробляючи звичайна прикладна програма Windows - створити таймер й організувати опрацювання від нього повідомлень, що періодично надходять WM_TIMER. Нагадаємо, що перемалювання вікна аплету виконується методом paint, що викликається віртуальною машиною Java несинхронізовано по відношенню до виконання іншого коду аплету. Можна чи скористуватися методом paint для періодичного перемалювання вікна аплету, організувавши в ньому, наприклад, нескінченний цикл з відставанням? На жаль, таким чином надходити в жодному випадку не можна. Метод paint після перемалювання вікна аплету повинен відразу повернути контроль, інакше робота аплету буде заблокована. Єдине виведення з створеного режиму - генерування потоку (або декількох трафіків), що будуть виконувати малювання у вікні аплету несинхронізовано по відношенню до роботи коду аплету. Наприклад, ви можете створити трафік, що періодичні відновлює вікно аплету, викликаючи для цього метод repaint, або малювати з трафіка безпосередньо у вікні аплету, отримавши заздалегідь для цього вікно об'єму відображення. 1.10.4. Синхронізація трафіківБагатопроцесний режим роботи відкриває нові можливості для програмістів, однак за ці можливості потрібно розраховуватися ускладненням процедури проектування прикладної програми та її налагодження. Головний важіль, з яким зштовхуються програмісти, які ще ніколи не створювали раніше багатопроцесних прикладних програм, це синхронізація одночасно працюючих трафіків. 1.10.4.1. А для чого потрібна синхронізація?Однопроцесна програма, така, наприклад, як програма MS-DOS, при старті одержує у монопольне розпорядження всі ресурси комп'ютера. Таким чином як в однопроцесній системі існує тільки одна процедура, він використовує ці ресурси в тому ланцюжку, що узгоджує логіку завдання програми. Процедури та трафіки, працюючі одночасно в багатопроцесній системі, можуть намагатися звертатися одночасно до одніх й тих же ресурсів, що може призвести до неадекватної роботи прикладної програми. Пояснимо це на простому прикладі. Нехай ми створюємо програму, що виконає операції з банківським рахунком. Операція зняття деякої суми грошей з рахунку може відбуватися в наступній черговості:
Якщо операція зменшення поточного рахунку виконується в однопроцесній системі, то жодних проблем не виникне. Однак уявимо собі, що дві процедури намагаються одночасно виконати щойно описану операцію з одним й тим же рахунком. Нехай при цьому на рахунку постійно перебує 5 млн. гривень, а обидві процедури намагаються зняти з нього по 3 млн. гривень. Припустимо, події розгортаються наступним чином:
Внаслідок дісталося, що з рахунку, на якому постійно перебувало 5 млн. гривень, було знято 6 млн. гривень, і при цьому там залишилося ще 2 млн. гривень! В загальному обліку - банку завданий шкоду в 3 млн. гривень. Як же скласти програму приведення рахунку, щоб вона не дозволяла коїти подібне? Дуже просто - на час виконання операцій над рахунком однією процедурою необхідно вимкнути доступ до цього рахунку з боку інших процедур. В цьому випадку сценарій роботи програми повинен бути наступним:
Коли перша процедура блокує рахунок, він стає недосяжний іншим процедурам. Якщо друга процедура також спробує заблокувати цей же рахунок, він буде переведена в стан чекання. Коли перша процедура зменшить рахунок та на ньому залишиться 2 млн. гривень, друга процедура буде розблокована. Він перевірить залишок, переконається, що сума недостатня й не буде проводити операцію. Таким чином, в багатопроцесному середовищі необхідна синхронізація трафіків у разі звертання до критичних ресурсів. Якщо над такими ресурсами будуть виконуватися операції в неадекватній черговості, це призведе до появі помилок , що тяжко виявляються. В мові програмування Java передбачене декілька засобів для синхронізації трафіків, які ми зараз розглянемо. 1.10.4.2. Синхронізація методівМожливість синхронізації немов би вкладена в кожний об'єкт, створюваний прикладною програмою Java. Для цього об'єкти постачаються клямками (рос. - «защёлками»), що можуть бути використані для блокування трафіків звертатися до цих об'єктів. Щоб скористуватися клямками, ви можете декларувати відповідний метод як synchronized, зробивши його синхронізованим: public synchronized void decrement () { ... } При виклику синхронізованого методу відповідний йому об'єкт (в якому він визначений) блокується для використання іншими синхронізованими методами. Внаслідок запобігається одночасний запис двома методами значень в ділянку пам'яті, що належить даному об'єкту. Використання синхронізованих методів - достатньо простий спосіб синхронізації трафіків, звертатися до загальних критичних ресурсів, подібне до описаного вище банківського рахунку. Помітимо, що не обов'язково синхронізувати весь метод - можна виконати синхронізацію тільки критичного фрагмента коду. ... synchronized (Account) { if (Account.check (3000000)) Account.decrement (3000000); } ... Тут синхронізація виконується для об'єкту Account. 1.10.4.3. Блокування трафікаСинхронізований трафік, визначений як метод типу synchronized, може переходити в заблокований стан автоматично при спробі посилання до ресурсу, зайнятого іншим синхронізованим методом, або при виконанні деяких операцій введення або виведення. Однак в ряді випадків корисно мати більш тонкі засоби синхронізації, що припускають явне використання по опитуванню прикладною програмою. 1.10.4.3.1. Блокування на специфіковану тривалість часу За допомогою методу sleep можна заблокувати трафік на специфіковану тривалість часу: try { Thread.sleep (500); } catch (InterruptedException ee) { ... } В даному прикладі робота трафіка Thread відкладається на 500 мілісекунд. Помітимо, що під час чекання трафік , що відкладе не віднімає ресурси процесора. Таким чином як метод sleep може створювати вилучення InterruptedException, необхідно передбачити його оброблення. Для цього ми використовували оператори try та catch. 1.10.4.3.2. Тимчасове припинення та поновлення роботи Методи suspend та resume дозволяють, відповідно, тимчасово відкладати та продовжувати роботу трафіка. В наступному фрагменті коду трафік m_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; } 1.10.4.3.3. Чекання повідомлення Якщо вам потрібне організувати взаємодію трафіків таким чином, щоб один трафік керував роботою іншого або інших трафіків, ви можете скористуватися методами wait, notify та notifyAll, визначеними в класі Object. Метод wait може використовуватися або з параметром, або без параметра. Цей метод перекладає трафік в стан чекання, в якому він буде постійно перебувати до тих пір, доки для трафіка не буде викликаний метод , що сповіщає notify, notifyAll, або доки не мине тривалість часу, означена в параметрі методу wait. Як користуватися методами wait, notify та notifyAll? Метод, що буде перекладатися в стан чекання, повинен бути синхронізованим, тобто його слідує описати як synchronized: public synchronized void run () { while (true) { ... try { this.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. 1.10.4.3.4. Чекання завершення трафіка За допомогою методу join ви можете виконувати чекання завершення роботи трафіка, для якої цей метод викликаний. Існує три визначення методу join: public final void join (); public final void join (long millis); public final void join (long millis, int nanos); Перший з них виконує чекання без обмеження в часу, для другого чекання буде перерване примусово через millis мілісекунд, а для третього - через millis мілісекунд та nanos наносекунд. Врахуйте, що дійсно ви не зможите вказувати час з вірогідністю до наносекунд, таким чином як дискретність системного годинника комп'ютера набагато більше. 1.10.5. Трафіки-демониВикликавши для трафіка метод setDaemon, ви перетворюєте звичайний трафік у трафік-демон. Такий трафік працює в фоновому режимі вимкнено від його трафіка , що породив. Якщо трафік-демон створює інші трафіки, то вони також стануть отримають статус трафіка-демона. Помітимо, що метод setDaemon необхідно викликати після генерування трафіка, але до моменту його запуску, тобто перед викликом методу start. За допомогою методу isDaemon ви можете перевірити, буде трафік демоном, або ні. 1.10.6. Аплет RectanglesУ вигляді прикладу багатопроцесної прикладної програми ми наведемо аплет Rectangles (мал. 38). Він створює три трафіка. Перший трафік малює в вікні аплету прямокутники спадкового розміру та кольору, другий - еліпси, а третій керує трафіком малювання еліпсів.
Мал. 38. Вікно аплету Rectangles Визначення місця прямокутників та еліпсів також вибирається спадково. 1.10.6.1. Текст-джерела аплету RectanglesТекст-джерела аплету Rectangles наведені в роздруку 1. /*Файл Rectangles.java*\ import java.applet.; import java.awt.; public class Rectangles extends Applet { DrawRectangles m_DrawRectThread=null; DrawEllipse m_DrawEllipseThread=null; NotifyTask m_NotifyTaskThread=null public String getAppletInfo () { return "Name: Rectangles"; } public void paint (Graphics g) { Dimension dimAppWndDimension=getSize (); 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); } 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 (); } if (m_NotifyTaskThread == null) { m_NotifyTaskThread= new NotifyTask (m_DrawEllipseThread); m_NotifyTaskThread.start (); } } public void stop () { if (m_DrawRectThread!=null) { m_DrawRectThread.stop (); m_DrawRectThread=null; } if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop (); m_DrawEllipseThread=null; } if (m_NotifyTaskThread!=null) { m_NotifyTaskThread.stop (); m_NotifyTaskThread=null; } } } class DrawRectangles extends Thread { Graphics g; Dimension dimAppWndDimension; public DrawRectangles (Applet Appl) { g=Appl.getGraphics (); dimAppWndDimension=Appl.getSize (); } 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); try { Thread.sleep (50); } catch (InterruptedException e) { stop (); } } } } class DrawEllipse extends Thread { Graphics g; Dimension dimAppWndDimension; public DrawEllipse (Applet Appl) { g=Appl.getGraphics (); dimAppWndDimension=Appl.getSize (); } 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.fillOval (x, y, width, height); try { this.wait (); } catch (InterruptedException e) { } } } } class NotifyTask extends Thread { Thread STask; public NotifyTask (Thread SynchroTask) { STask=SynchroTask; } public void run () { while (true) { try { Thread.sleep (30); } catch (InterruptedException e) { } synchronized (STask) { STask.notify (); } } } } 1.10.6.2. Опис текст-джерел аплету RectanglesВ цій прикладній програмі ми створюємо на базі класу Thread три класи. Перший з них передвизначений для генерування трафіка малювання прямокутників, другий - для генерування трафіка малювання зафарбованих еліпсів, а третій - для контролю трафіком малювання еліпсів. Що до базисного класу аплету, то він успадкований, як звичайно, від класу Applet та не реалізує інтерфейс Runnable: public class Rectangles extends Applet { ... } 1.10.6.2.1. Поля класу Rectangles В класі Rectangles ми визначили три поля з іменами m_DrawRectThread, m_DrawEllipseThread та m_NotifyTaskThread: DrawRectangles m_DrawRectThread=null; DrawEllipse m_DrawEllipseThread=null; NotifyTask m_NotifyTaskThread=null Ці поля будуть посиланнями на класи, відповідно DrawRectangles, DrawEllipse і NotifyTask. Перший з них створений для малювання прямокутників, другий - еліпсів, а третій - для контролю трафіком малювання еліпсів. Означені поля ініціалізуються значенням null, що узгоджує непрацюючі або нестворені завдання. 1.10.6.2.2. Метод start класу Rectangles Цей метод послідовно створює три трафіки й запускає їх на виконання: if (m_DrawRectThread == null) { m_DrawRectThread= new DrawRectangles (this); m_DrawRectThread.start (); } if (m_DrawEllipseThread == null) { m_DrawEllipseThread= new DrawEllipse (this); m_DrawEllipseThread.start (); } if (m_NotifyTaskThread == null) { m_NotifyTaskThread= new NotifyTask (m_DrawEllipseThread); m_NotifyTaskThread.start (); } У вигляді параметра розробникам класів DrawRectangles та DrawEllipse ми передаємо звертання на аплет Rectangles. Це звертання буде потрібне для одержання об'єму відображення та малювання геометричних фігур. Трафік класу NotifyTask буде керувати роботою трафіка DrawEllipse, тому ми передаємо його розробнику звертанню на відповідний об'єкт m_DrawEllipseThread. 1.10. 6.2. 2. Метод stop класу Rectangles Коли користувач покидає сторінку сервера Web з аплетом, метод stop класу Rectangles послідовно зупиняє gjnjrb малювання прямокутників та еліпсів, а також для керування трафік: if (m_DrawRectThread!=null) { m_DrawRectThread.stop (); m_DrawRectThread=null; } if (m_DrawEllipseThread == null) { m_DrawEllipseThread.stop (); m_DrawEllipseThread=null; } if (m_NotifyTaskThread!=null) { m_NotifyTaskThread.stop (); m_NotifyTaskThread=null; } 1.10.6.2.3. Поля класу DrawRectangles Клас DrawRectangles визначений для трафіка малювання прямокутників: class DrawRectangles extends Thread { ... } У полі g класу запам'ятовується об'єм відображення вікна аплету, а у полі dimAppWndDimension - розміри цього вікна: Graphics g; Dimension dimAppWndDimension; Значення цих полів визначаться розробником класу по звертанню на головний клас аплету. 1.10.6.2.4. Розробник класу DrawRectangles У вигляді параметра розробнику передається звертання на клас аплету. Розробник використовує це звертання для одержання та збереження в полях класу об'єму відображення та розмірів вікна аплету: public DrawRectangles (Applet Appl) { g=Appl.getGraphics (); dimAppWndDimension=Appl.getSize (); } 1.10.6.2.5. Метод run класу DrawRectangles Програмний код методу run працює в рамках окремого трафіка. Він малює у вікні аплету зафарбовані прямокутники. Прямокутники мають спадкові координати, визначення місця та колір. Для того щоб малювати, необхідно отримати об'єм відображення. Цей об'єм був отриманий розробником класу DrawRectangles та може бути використаний методом run. Озброєний об'ємом відображення та розмірами вікна аплету, трафік входить до нескінченного циклу малювання прямокутників. У вигляді генератора випадкових чисел ми використовуємо метод 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. 1.10.6.2.6. Метод run класу DrawEllipse Клас DrawEllipse дуже схожий на щойно розглянутий клас DrawRectangles. Відмінність полягає тільки у фінальному фрагменті методу run, що ми й розглянемо. Замість відставання на 50 мілісекунд метод run з класу DrawEllipse переходить в стан чекання повідомлення, викликаючи метод wait: try { this.wait (); } catch (InterruptedException e) { } Це повідомлення створюється для керування трафіком класу NotifyTask, до опису якого ми переходимо. 1.10.6.2.7. Поля класу NotifyTask В класі NotifyTask ми визначили одне поле STask класу Thread. Це поле що запам'ятовує звертання на трафік, роботою якого керує даний клас: class NotifyTask extends Thread { Thread STask; ... } 1.10.6.2.8. Розробник класу NotifyTask Розробник класу NotifyTask записує в поле STask звертання на гілку малювання еліпсів: public NotifyTask (Thread SynchroTask) { STask=SynchroTask; } 1.10.6.2.9. Метод run класу NotifyTask Метод run класу NotifyTask періодичні розблокує трафік малювання еліпсів, викликаючи для цього метод notify у циклі з відставанням 30 мілісекунд. Звертання до об'єкту STask, що запам'ятовує звертання на трафік малювання еліпсів, виконується з використанням синхронізації: public void run () { while (true) { try { Thread.sleep (30); } catch (InterruptedException e) { } synchronized (STask) { STask.notify (); } } }
1.11. РОБОТА З ФАЙЛАМИ
Бібліотека класів мови програмування Java містить численні засоби, призначені для роботи з файлами. Та хоча аплети не мають доступу до локальних файлів, розташованих на комп'ютері користувача, вони можуть звертатися до файлів, що постійно перебують в директорії сервера Web. Автономна прикладна програма Java може працювати як з локальними, так й з віддаленими файлами (через мережу Internet або Intranet). В будь-якому випадку, будете чи ви створювати автономну прикладну програму Java або аплети, взаємодіяти сервером Web через мережу, ви повинні ознайомитися з класами, призначеними для організації введення та виведення. 1.11.1. Класи Java для роботи з трафікамиПрограміст, що створює автономну прикладну програму Java, може працювати з трафіками декількох типів: •Стандартні трафіки введення та виведення; •Трафіки, спаровані з локальними файлами; •Трафіки, спаровані з файлами в оперативній пам'яті; •Трафіки, спаровані з віддаленими файлами Розглянемо стисло класи, спаровані з трафіками. 1.11.1.1. Стандартні трафікиДля роботи зі стандартними трафіками у класі System існує три статичних об'єкти: System.in, System.out та System.err. За своїм призначенням ці трафіки більше всього нагадують стандартні трафіки введення, виведення та виведення повідомлення про помилки операційної системи MS-DOS. Трафік System.in з'єднаний з клавіатурою, трафік System.out та System.err - з консолем прикладної програми Java. 1.11.1.2. Базові класи для роботи з файлами та трафікамиКількість класів, створених для роботи з файлами, достатньо велика, щоб призвести початківця програміста до розгубленості. Перш ніж ми займемося конкретними класами та наведемо приклади прикладної програми, працюючої з трафіками та файлами, розглянемо ієрархію класів, призначених для організації введення та виведення. Всі базисні класи, які зацікавлять нас в цьому розділіі, пішли від класу Object (мал. 39).
Мал. 39. Базисні класи для роботи з файлами та трафіками 1.11.1.2.1. Клас InputStream Клас InputStream буде базисним для великої кількості класів, на основі яких створюються трафіки введення. Саме похідні класи застосовуються програмістами, оскільки в них існують набагато більш потужні методи, ніж в класі InputStream. Ці методи дозволяють працювати з трафіком введення не на рівні окремих байт, а на рівні об'єктів різних класів, наприклад, класу String та інших. 1.11.1.2.2. Клас OutputStream Аналогічно, клас OutputStream виступає у вигляді базисного для різних класів, що мають відношення до трафіків виведення. 1.11.1.2.3. Клас RandomAccesFile За допомогою класу RandomAccesFile можна організувати роботу з файлами в режимі довільного доступу, коли програма вказує зміщення та обсяг блоку даних, над яким виконується операція введення або виведення. Помітимо, до речі, що класи InputStream та OutputStream також можна використовувати для звертання до файлів в режимі довільного (прямого) доступу. 1.11.1.2.4. Клас File Клас File передвизначений для роботи з змістами директорій. За допомогою цього класу можна отримати перелік файлів та директорій, розташованих в специфікованій директорії, створити або вилучити директорію, перейменувати файл або директорію, а також виконати деякі інші операції. 1.11.1.2.5. Клас FileDescriptor C допомогою класу FileDescriptor ви можете перевірити специфікатор формату активного файлу. 1.11.1.2.6. Клас StreamTokenizer Дуже зручний клас StreamTokenizer. Він дозволяє організувати виділення з вхідного потоку даних елементів, відділених друг від друга специфікованим розділовим знаком, таким, наприклад, як кома, прогалина, символи повернення каретки та зміни рядка. 1.11.1.3. Похідні від класу InputStreamВід класу InputStream виробляється багато інших класів, як це показане на мал. 40.
Мал. 40. Класи, похідні від класу InputStream 1.11. 1.3. 1. Клас FilterInputStream Клас FilterInputStream, що походить безпосередньо від класу InputStream, буде абстрактним класом, на базі якого створені класи BufferedInputStream, DataInputStream, LineNumberInputStream та PushBackInputStream (які чудові назви класів - майже на німецькій мові!). Безпосередньо клас FilterInputStream не використовується у прикладній програмі Java, таким чином як, по-перше, він буде абстрактним та передвизначений для перевизначення методів базового класу InputStream, а по-друге, найбільш корисні методи для роботи з трафіками введення існують в класах, створених на базі класу FilterInputStream. 1.11.1.3.2. Клас BufferedInputStream Буферизація операцій введення та виведення в більшості випадків значно прискорює роботу прикладної програми, таким чином як при її використанні скорочується кількість посилань до системи для обміну даними з зовнішніми пристроями. Клас BufferedInputStream може бути використаний прикладною програмою Java для організації трафіків введення, що довантажувалися. Помітимо, що розробники цього класу у вигляді параметра одержують звертання на об'єкт класу InputStream. Таким чином, ви не можете просто створити об'єкт класу BufferedInputStream, не створивши перед цим об'єкту класу InputStream. Подробиці ми обговоримо пізніше. 1.11.1.3.3. Клас DataInputStream Складаючи програми на мові програмування C, ви були примушені працювати з трафіками на рівні байт або, в кращому випадку, на рівні текстових рядків. Однак часто виникає необхідність записувати в потік даних та читати звідти об'єкти інших типів, наприклад, цілі числа та числа типу double, числа в форматі з рухомою десятковою комою (яка десь плаває), масиви байтів та літер й таке інше. Клас DataInputStream містить методи, що дозволять витягати з вхідного потоку дані в перерахованих вище форматах або, як говорять, виконувати форматоване введення даних. Він також реалізує інтерфейс DataInput, що заступає для цієї же мети. Тому клас DataInputStream дуже зручний й часто застосовується у прикладній програмі для роботи з трафіками введення. Таким же чином як й розробник класу BufferedInputStream, розробник класу DataInputStream повинен отримати через свої параметри звертання на об'єкт класу InputStream. 1.11.1.3.4. Клас LineNumberInputStream За допомогою класу LineNumberInputStream ви можете працювати з літерно-цифровими трафіками, складатися з окремих рядків, поєднаних символами зміни рядка\r та безумовного переходу на наступний рядок\n. Методи цього класу дозволяють стежити за нумерацією рядків в таких трафіках. 1.11.1.3.5. Клас PushBackInputStream Клас PushBackInputStream дозволяє повернути в трафік введення щойно прочитану звідти літеру, з тим щоб після цього цю літеру можна було прочитати знову. 1.11.1.3.6. Клас ByteArrayInputStream У разі необхідності ви можете створити в прикладній програмі Java вхідний потік даних не на базі локального або віддаленого файлу, а на базі масиву, розташованого в оперативній пам'яті. Клас ByteArrayInputStream передвизначений саме для цього - ви передаєте розробнику класу звертанню на масив, й одержуєте вхідний потік даних, спарований з цим масивом. Трафіки в оперативній пам'яті можуть бути використані для тимчасового зберігання даних. Помітимо, що таким чином як аплети Java не можуть звертатися до локальних файлів, для генерування тимчасових файлів можна використовувати трафіки в оперативній пам'яті на базі класу ByteArrayInputStream. Іншу можливість надає клас StringBufferInputStream, розглянутий нижче. 1.11.1.3.7. Клас StringBufferInputStream Клас StringBufferInputStream дозволяє створювати трафіки введення на базі рядків класу String, використовуючи при цьому тільки молодші байти, що запам'ятовуються в такому рядку символів. Цей клас може заступати доповненням для класу ByteArrayInputStream, що також передвизначений для генерування трафіків на базі даних з оперативної пам'яті. 1.11.1.3.8. Клас FileInputStream Цей клас дозволяє створити трафік введення на базі класу File або FileDescriptor. 1.11.1.3.9. Клас PipedInputStream За допомогою класів PipedInputStream та PipedOutputStream можна організувати двобічний обмін даними між двома одночасно працюючими завданнями багатозадачного аплету. 1.11.1.3.10. Клас SequenceInputStream Клас SequenceInputStream дозволяє об'єднати декілька вхідних трафкіів в один трафік. Якщо в процедурі читання буде досягнуть кінець першого трафіка такого об'єднання, надалі читання буде виконуватися з другого трафіка й таким чином далі. 1.11.1.4. Похідні від класу OutputStreamКлас OutputStream передвизначений для генерування трафіків виведення. Прикладна програма, як правило, безпосередньо не використовує цей клас для операцій виведення, таким же чином як й клас InputStream для операцій введення. Замість цього застосовуються класи, ієрархія яких показана на мал. 41.
Мал. 41. Класи, похідні від класу OutputtStream Розглянемо стисло призначення цих класів. 1.11.1.4.1. Клас FilterOutputStream Абстрактний клас FilterOutputStream заступає прошарком між класом OutputStream та класами BufferedOutputStream, DataOutputStream, а також PrintStream. Він виконує роль, аналогічну ролі розглянутого раніше класу FilterIntputStream. 1.11.1.4.2. Клас BufferedOutputStream Клас BufferedOutputStream передвизначений для генерування трафіків виведення, що довантажувалися. Як ми вже говорили, буферизація прискорює роботу прикладної програми з трафіками. 1.11.1.4.3. Клас DataOutputStream За допомогою класу DataOutputStream прикладної програми Java можуть виконувати форматоване виведення даних. Для введення структурованих даних ви повинні створити вхідний трафік з використанням класу DataInputStream, про який ми вже говорили. Клас DataOutputStream реалізує інтерфейс DataOutput. 1.11.1.4.4. Клас PrintStream Трафіки, створені з використанням класу PrintStream, передвизначені для розміченого виведення даних різних типів з метою їхнього візуального виразу у вигляді текстового рядка. Аналогічна операція в мові програмування C виконувалася функцією printf. 1.11.1.4.5. Клас ByteArrayOutputStream За допомогою класу ByteArrayOutputStream можна створити трафік виведення в оперативній пам'яті. 1.11.1.4.6. Клас FileOutputStream Цей клас дозволяє створити трафік виведення на базі класу File або FileDescriptor. 1.11.1.4 7. Клас PipedOutputStream Як ми вже говорили, класи PipedInputStream та PipedOutputStream передвизначені для організації двобічноuj обмінe даними між двома одночасно працюючими вітками багатозадачного аплету. 1.11.2. Робота зі стандартними трафікамиПрикладній програмі Java досяжні три стандартних трафіка, що завжди відкриті: стандартний трафік введення, стандартний трафік виведення та стандартний трафік виведення повідомлення про помилки. Всі перераховані вище трафіки визначені в класі System як статичні поля з іменами, відповідно, in, out та err: public static PrintStream err; public static InputStream in; public static PrintStream out; Помітимо, що стандартні трафіки, як правило, не використовуються аплетами, оскільки браузери спілкуються з користувачем через вікно аплету та повідомлення від мишки й клавіатури, а не через консоль. 1.11.2.1. Стандартний трафік введенняСтандартний трафік введення in визначений як статичний об'єкт класу InputStream, що містить тільки примітивні методи для введення даних. Згодиться більш за все вам метод read: public int read (byte b []); Цей метод читає дані з трафіка до масива, звертання на який передається через єдиний параметр. Кількість прочитаних даних визначиться розміром масиву, тобто значенням b.length. Метод read вертає кількість прочитаних байт даних або - 1, якщо досягнутий кінець трафіка. При появі помилок створюється вилучення IOException, оброблення якого необхідно передбачити. 1.11.2.2. Стандартний трафік виведенняСтандартний трафік виводу 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 без параметрів записує тільки знак безумовного переходу на наступний рядок. 1.11.2.3. Стандартний трафік виведення повідомлення про помилкиСтандартний трафік виведення повідомлення про помилки err таким же чином, як й стандартний трафік виведення out, створений на базі класу PrintStream. Тому для запису повідомлення про помилки ви можете використовувати щойно описані методи print та println. 1.11.3. Генерування трафіків, спарованих з файламиЯкщо вам потрібно створити вхідний або вихідний трафік, спарований з локальним файлом, слідує скористуватися класами з бібліотеки Java, створеними на базі класів InputStream та OutputStream. Ми вже стисло розповідали про ці класи у розділі "Класи Java для роботи з трафіками". Однак процедура використання перерахованих в цьому розділі класів може показатися досить чудної. Говорячи стисло, чудасія полягає в тому, що для генерування трафіка вам необхідно скористуватися відразу декількома класами, а не однім, найбільш підхожим для розв'язування поставленої задачі, як це можна було би припустити. Пояснимо сказане на прикладі. Нехай, наприклад, вам потрібний вихідний потік для запису структурованих даних (скажемо, текстових рядків класу String). Здавалося б, достатньо створити об'єкт класу DataOutputStream, - й діло зроблене. Однак не всі так просто. В класі DataOutputStream передбачений тільки один розробник, якому у вигляді параметра необхідно передати звертання на об'єкт класу OutputStream: public DataOutputStream (OutputStream out); Що до розробника класу OutputStream, то він має наступний вигляд: public OutputStream (); Таким чином ні в першому, ні в другом розробнику не передбачене жодних посилань на файли, то незрозуміло, як з використанням тільки одніх класів OutputStream та DataOutputStream можна створити вихідний потік, спарований з файлом. Що ж робити? 1.11.3.1. Генерування трафіка для форматованого обміну данимиВиявляється, генерування трафіків, спарованих з файлами та призначених для форматованого введення або виведення, необхідно виконувати в декілька прийомів. При цьому спочатку необхідно створити трафіки на базі класу FileOutputStream або FileInputStream, а після цього передати звертання на створений трафік розробнику класу DataOutputStream або DataInputStream. В класах FileOutputStream та FileInputStream передбачені розробники, яким у вигляді параметра передається або звертання на об'єкт класу File, або звертання на об'єкт класу FileDescriptor, або, нарешті, текстовий рядок шляху до файлу: public FileOutputStream (File file); public FileOutputStream ( FileDescriptor fdObj); public FileOutputStream (String name); Таким чином, якщо вам потрібний вихідний трафік для запису структурованих даних, спочатку ви створюєте трафік як об'єкт класу FileOutputStream. Після цього звертання на цей об'єкт слідує передати розробнику класу DataOutputStream. Отриманий таким чином об'єкт класу DataOutputStream можна використовувати як вихідний трафік, записуючи в нього структуровані дані. 1.11.3.2. Додавання буферизаціїА що, якщо вам потрібний не простий вихідний потік, а що довантажувався (тобо потрібен буферизований трафік) ? Тут вам може допомогти клас BufferedOutputStream. Ось два розробника, передбачених в цьому класі: public BufferedOutputStream ( OutputStream out); public BufferedOutputStream ( OutputStream out, int size); Перший з них створює вихідний трафік , що довантажувався на базі трафіка класу OutputStream, а другий робить те ж саме, але додатково дозволяє вказати розмір буфера в байтах. Якщо вам потрібне створити об'єктний трафік , який довантажувався для запису структурованих даних, генерування трафіка виконується в три кроки:
Ось фрагмент текст-джерела програми, що створює об'єктний трафік , що довантажувався для запису структурованих даних в файл з ім'ям output.txt: DataOutputStream OutStream; OutStream=new DataOutputStream ( new BufferedOutputStream ( new FileOutputStream ("output.txt"))); Аналогічним чином створюється трафік введення, що довантажувався для читання структурованих даних з того же файлу: DataInputStream InStream; InStream=new DataInputStream ( new BufferedInputStream ( new FileInputStream ("output.txt"))); 1.11.3.3. Вилучення при генеруванні трафіківПри генеруванні трафіків на базі класів FileOutputStream й FileInputStream можуть виникати вилучення FileNotFoundException, SecurityException, IOException. Вилучення FileNotFoundException виникає при спробі відкрити вхідний потік даних для неіснуючого файлу, тобто коли файл не знайдений. Вилучення SecurityException виникає при спробі відкрити файл, для якого вимкнений доступ. Наприклад, якщо файл можна тільки читати, а він відкривається для запису, виникне вилучення SecurityException. Якщо файл не може бути відкритий для запису по якимось іншим причинам, виникає вилучення IOException. 1.11.4. Запис даних в трафік та читання даних з трафікаДля обміну даними з трафіками можна використовувати як примітивні методи write й read, так й методи, що припускають введення або виведення структурованих даних. Залежно від того, на базі якого класу створений трафік, залежить комплект досяжних методів, призначених для читання або запису даних. 1.11.4.1. Примітивні методиСтворивши вихідний потік на базі класу 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. 1.11.4.2. Методи для читання та запису структурованих данихНатомість щоб записувати в трафіки й читати звідти окремі байти або масиви байт, програмісти звичайно віддають перевагу користуванню набагато більш зручним методам класів 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. Перше з них виникає в разі помилки, а друге - при досягненні кінця вхідного потоку в процедурі читання. 1.11.5. Закриття трафіківПрацюючи з файлами в середовищі MS-DOS або Windows засобами мови програмування C ви повинні були закривати непотрібні більш файли. Оскільки в системі інтерпретації прикладної програми Java є процедура збирання сміття, виникає питання - виконує чи він автоматичне закриття трафіків, з якими прикладна програма завершила роботу? Виявляється, процедура збирання сміття не робить нічого подібного! Збирання сміття виконується тільки для об'єктів, розміщених в оперативній пам'яті. Трафіки ви повинні закривати явним чином, викликаючи для цього метод close. 1.11.6. Примусове повернення у початковий стан буферівЩе один важливий момент поєднаний з трафіками , що довантажувалися. Як ми вже говорили, буферизація прискорює роботу прикладної програми з трафіками, бо при її використанні скорочується кількість посилань до системи введення/виведення. Ви можете поступово на протязі дня додавати в трафік дані по одному байту, та тільки увечері ці дані будуть фізічно записані в файл на накопичувачі. В численних випадках, однак, прикладна програма повинно, не відмовляючись зовсім від буферизації, виконувати примусовий запис буферів в файл. Це можна зробити за допомогою методу flush. 1.11.7. Трафіки в оперативній пам'ятіОпераційні системи Windows 95 та Windows NT надають можливість для програміста працювати з оперативною пам'яттю як з файлом. Це дуже зручно в численних випадках. Зокрема, файли, що відображаються на оперативну пам'ять, можна використовувати для передавання даних між одночасно працюючими вітками та процедурами. При генеруванні прикладної програми й аплетів Java ви також можете працювати з об'єктами оперативної пам'яті, як з файлами, а точніше говорячи, як з трафіками. Таким чином як аплетам вимкнене звертатися до файлів, розташованих на локальному дискові комп'ютера, при необхідності генерування тимчасових трафіків введення або виведення останні можуть бути розміщені в оперативній пам'яті. Раніше ми позначали, що в бібліотеці класів Java є три класи, спеціально призначених для генерування трафіків в оперативній пам'яті. Це класи ByteArrayOutputStream, ByteArrayInputStream та StringBufferInputStream. 1.11.7.1. Клас 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. 1.11.7.2. Клас 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, що дозволяє організувати розмічене введення даних. 1.11.7.3. Клас StringBufferInputStreamКлас StringBufferInputStream передвизначений для генерування вхідного потоку на базі текстового рядка класу String. Звертання на цей рядок передається розробнику класу StringBufferInputStream через параметр: public StringBufferInputStream (String s); В класі StringBufferInputStream визначені ті же методи, що й в щойно розглянутому класі ByteArrayInputStream. Для більш зручної роботи ви, певно, створите на базі трафіка класу StringBufferInputStream трафік класу DataInputStream. 1.11.8. Клас StreamTokenizer для розбору вхідних потокуЯкщо ви створюєте прикладну програму, призначене для оброблення тексту (наприклад, конвертор або просто розбирач файлу конфігурації, що містить значення різних параметрів), вам може згодитися клас StreamTokenizer. Створивши об'єкт цього класу для вхідного потоку, ви можете легко розв'язати задачу виділення з цього трафіка окремих слів, літер, чисел та рядків коментаря. 1.11.8.1. Розробник класу StreamTokenizerДля генерування об'єктів класу StreamTokenizer передбачений всього один розробник: public StreamTokenizer (InputStream istream); У вигляді параметра цьому розробнику необхідно передати звертання на заздалегідь створений вхідний трафік. 1.11.8.2. Методи класу StreamTokenizerДля вирівнювання параметрів розбирача StreamTokenizer та одержання окремих елементів вхідного потоку ви повинні користуватися методами, визначеними у класі StreamTokenizer. Розглянемо самі важливі з них. 1.11.8.3. Методи для вирівнювання параметрів розбирачаНижче ми навели дослідний зразок методів, призначених для вирівнювання параметрів розбирача: 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 дозволяє вказати літери, що будуть елементами слова. 1.11.8.4. Методи для розбору вхідного потокуПісля того як ви створили розбирач вхідного потоку на базі класу StreamTokenizer та встановили його параметри за допомогою описаних вище методів, можна приступати власне до розбирання трафіка. Звичайно для цього організується цикл, в якому викликається метод nextToken: public int nextToken (); Цей метод може повернути одне з наступних значень:
Як витягти елементи трафіка, що зчитуються? В класі 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 (); 1.11.9. Клас StringTokenizerРозповідаючи про клас StreamTokenizer, не можна не згадати про другий клас зі схожою назвою й призначенням, а саме про клас StringTokenizer. Визначення цього класу достатньо компактно. 1.11.9.1. Розробникиpublic StringTokenizer (String str); public StringTokenizer (String str, String delim); public StringTokenizer (String str, String delim, boolean returnTokens); 1.11.9.2. Методиpublic String nextToken (); public String nextToken (String delim); public int countTokenss (); public boolean hasMoreElements (); public boolean hasMoreTokenss (); public Object nextElement (); Клас StringTokenizer не має жодної залежності до трафіків, оскільки передвизначений для виділення окремих елементів з рядків типу String. Розробники класу одержують у якості першого параметра str звертання на розбраний рядок. Другий параметр delim, якщо він є, специфікує розділовий знак, з використанням яких в рядку будуть виділятися елементи. Параметр returnTokens визначає, чи треба повертати виявлений розділовий знак як елементи розібраного рядка. Розглянемо стисло методи класу StringTokenizer. Для розбору рядка прикладна програма повинно організувати цикл, викликаючи в ньому метод nextToken. Умовою завершення циклу може бути або поява вилучення NoSuchElementException, або повернення значення false методами hasMoreElements або hasMoreTokens. Метод countTokens дозволяє визначити, скільки разів був викликаний метод nextToken перед появою вилучення NoSuchElementException. 1.11.10. Робота з файлами та директоріями за допомогою класу FileВ попередніх розділах ми розглянули класи, призначені для читання й запису трафіків. Однак часто виникає необхідність виконання й таких операцій, як визначення атрибутів файлу, генерування або вилучення директорій, вилучення файлів, одержання переліку всіх файлів у директорії й таким чином далі. Для виконання всіх цих операцій в прикладній програмі Java використовується клас з ім'ям File. 1.11. 10.1. Генерування об'єкту класу 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"); 1.11.10.2. Визначення властивостей файлів і директорійПісля того як ви створили об'єкт класу File, неважко визначити властивості цього об'єкту, скористувавшись відповідними методами класу File. 1.11.10.3. Перевірка існування файлу або директоріїЗа допомогою методу exists ви можете перевірити існування файлу або директорії, для якого був створений об'єкт класу File: public boolean exists (); Цей метод можна застосовувати перед генеруванням трафіка на базі класу FileOutputStream, якщо вам потрібне уникнути спадкового перезапису існуючого файлу. В цьому випадку перед генеруванням вихідного трафіка класу FileOutputStream слідує створити об'єкт класу File, вказавши розробнику шлях до файлу, а після цього перевірити існування файлу методом exists. 1.11.10.4. Перевірка можливості читання та записуМетоди canRead та canWrite дозволяють перевірити можливість читання з файлу та занесення, відповідно: public boolean canRead (); public boolean canWrite (); Їх корисно застосовувати перед генеруванням відповідних трафіків, якщо потрібно уникнути появи вилучень, спарованих зі спробою виконання доступу недозволеного типу. Якщо доступ дозволений, ці методи вертають значення true, а якщо вимкнений - false. 1.11.10.5. Визначення типу об'єкту - файл або директоріяЗа допомогою методів isDirectory та isFile ви можете перевірити, чому відповідає створений об'єкт класу File - директорії або файлу: public boolean isDirectory (); public boolean isFile (); 1.11.10.6. Одержання ім'я файлу або директоріїМетод getName вертає ім'я файлу або директорії для специфікованого об'єкту класу File (ім'я виділяється зі шляху): public String getName (); 1.11.10.7. Одержання абсолютного шляху до директоріїМетод getAbsolutePath вертає абсолютний шлях до файлу або директорії, який може бути машинно-залежним: public String getAbsolutePath (); 1.11.10.8. Визначення типу означеного шляху - абсолютний або відноснийЗа допомогою методу isAbsolute ви можете визначити, узгоджується чи даний об'єкт класу File файлу або директорії, специфікованій абсолютним (повним) шляхом, або відносним складеним ім'ям: public boolean isAbsolute (); 1.11.10.9. Визначення маршруту до файлу або директоріїМетод getPath дозволяє визначити машинно-незалежний шлях файлу або директорії: public String getPath (); 1.11.10.10. Визначення батьківської директоріїЯкщо вам потрібне визначити батьківську директорію для об'єкту класу File, то це можна зробити методом getParent: public String getParent (); 1.11.10.11. Визначення довжини файлу в байтахДовжину файлу в байтах можна визначити за допомогою методу length: public long length (); 1.11.10.12. Визначення часу останньої модифікації файлу або директоріїДля визначення часу останньої модифікації файлу або директорії ви можете викликати метод lastModified: public long lastModified (); Помітимо, однак, що цей метод вертає час в відносних одиницях з момента запуску системи, тому його зручно використовувати тільки для відносних порівнянь. 1.11.10.13. Одержання літерно-цифрового виразу об'єктуМетод toString вертає текстовий рядок, подаючий об'єкт класу File: public String toString (); 1.11.10.14. Одержання значення геш-кодуМетод hashCode вертає значення геш-коду, відповідного об'єкту File: public int hashCode (); 1.11.10.15. Вилучення файлів та директорійДля вилучення непотрібного файлу або директорії ви повинні створити відповідний об'єкт File та після цього викликати метод delete: public boolean delete (); 1.11. 10.16. Генерування директорійЗа допомогою методів mkdir і mkdirs можна створювати нові директорії: public boolean mkdir (); public boolean mkdirs (); Перший з цих методів створює одну директорію, другий - всі директорії, базові до створюваної директорії (тобто повний шлях). 1.11.10.17. Перейменування файлів та директорійДля перейменування файлу або директорії ви повинні створити два об'єкти класу File, один з яких узгоджує старе ім'я, а другий - нове. Після цього для першого з цих об'єктів потрібне викликати метод renameTo, вказавши йому у вигляді параметра звертання на другий об'єкт: public boolean renameTo (File dest); В разі успіха метод вертає значення true, при появі помилки - false. Може також виникати вилучення SecurityException. 1.11.10.18. Порівнювання об'єктів класу FileДля порівнювання об'єктів класу File ви повинні використовувати метод equals: public boolean equals (Object obj); Помітимо, що цей метод порівнює шляхи до файлів та директорій, але не самі файли або директорії. 1.11.10.19. Одержання переліку змісту директоріїЗа допомогою методу list ви можете отримати перелік вмісту директорії, відповідної даному об'єкту класу File. В класі File передбачене два варіанту цього методу - без параметра та з параметром: public String [] list (); public String [] list (FilenameFilter filter); Перший з цих методів вертає масив рядків з іменами змісту директорії, не вмикаючи поточний каталог й батьківську директорію. Другий дозволяє отримати перелік не всіх об'єктів, що запам'ятовуються в директорії, а тільки тих, що задовольняють умовам, визначеним в фільтрі filter класу FilenameFilter. 1.11.11. Довільний доступ до файлівВ рядку випадків, наприклад, при генеруванні скбд, вимагається забезпечити довільний (прямий) доступ до файлу. Розглянуті нами раніше трафіки введення та виведення придатні лише для послідовного доступу, таким чином як у відповідних класах немає засобів позиціонування всередині файлу. Між тим бібліотека класів 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 readUnsignedBytee (); 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 writeBytee (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); Імена наведених методів говорять самі за себе, тому ми не будемо їх описувати. 1.11.12. Прикладна програма StreamTokenВ прикладній програмі StreamToken ми демонструємо використання класу StreamTokenizer для розбору вхідного потоку. Спочатку прикладна програма запрошує у користувача рядок для розбору, записуючи її в файл. Після цього цей файл відкривається для читання трафіком з довантажуванням та розбирається на складові елементи. Кожний такий елемент виводиться в окремому рядку, як це показане на мал. 42.
Мал. 42. Розбір вхідного потоку в прикладній програмі StreamToken Зверніть увагу, що в процедурі розбору значення 3.14 було сприйняте як числове, а 3,14 - ні. Це тому, що при настроюванні розбирача ми вказали, що знак',' є звичайним. 1.11.12.1. Текст-джерело прикладної програмиТекст-джерело прикладної програми StreamToken уявлене нижче в роздруку. /*Файл StreamToken.java*\ import java.io.; public class StreamToken { 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); sOut=new String (bKbdInput, 0); OutStream=new DataOutputStream ( new BufferedOutputStream ( new FileOutputStream ( "output.txt"))); 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 <Enter> to terminate... "); System.in.read (bKbdInput); } catch (Exception ioe) { System.out.println (ioe.toString ()); } } } 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; } 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 ()); } } } 1.11.12.2. Опис текст-джерела прикладної програмиПісля введення рядка з клавіатури та запису її в файл через трафік наша прикладна програма створює вхідний трафік , що довантажувався, як це показана нижче: 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); 1.11.13. Прикладна програма DirectFileДля ілюстрації способів роботи з класом RandomAccessFile ми підготували прикладну програму DirectFile, в якому створюється невелика база даних. Ця база даних складається з двох файлів: файлу даних та файлу вказівника. В файлі даних запам'ятовуються записи, що складаються з двох полів - літерно-цифрового та числового. Літерно-цифрове поле з назвою name запам'ятовує рядки з ознакою кінця рядка"\r\n", а числове з назвою account - значення типу int. В меню File нашої прикладної програми є рядки New та View records (мал. 43).
Мал. 43. Лінійка меню File За допомогою рядка New ви можете створити базу даних, що може складатися з трьох записів. Якщо вибрати з меню File рядок View records, на відображувальному екрані з'явиться діалогова панель зі змістом цих записів (мал. 44).
Мал. 44. Зміст трьох перших полів бази даних Замість знаку зміни рядка в діалоговій панелі відображається маленький квадратик. Відбиток створюваного файлу даних наведений на мал. 45.
Мал. 45. Дамп файлу даних З цього дампу видно, що після першого запуску прикладної програми в файлі даних існують наступні записи:
При наступних операціях кожного разу в файл даних будуть додаватися наведені вище записи. Таким чином як поле name має змінну довжину, для забезпечення можливості довільного доступу до запису по її номеру необхідно десь запам'ятовувати зсуви всіх записів. Ми це робимо в файлі індексів, дамп якого представлений на мал. 46.
Мал. 46. Дамп файлу вказівника Файл індексів запам'ятовує 8-байтовые зсуви записів файлу даних в форматі long. Знаючи номер запису, можна легко обчислити зміщення в файлі індексів, за яким запам'ятовується зміщення потрібного запису в файлі даних. Якщо витягти це зміщення, то можна виконати позиціонування в файлі даних з метою читання потрібного запису, що й робить наша прикладна програма. 1.11.13.1. Текст-джерело прикладної програми DirectFileТекст-джерело прикладної програми DirectFile уявлене нижче в роздруку. /*Файл DirectFile.java*\ import java.awt.; import java.io.; import java.util.; public class DirectFile { public static void main (String args []) { MainFrameWnd frame= new MainFrameWnd ("MenuApp"); frame.setSize ( frame.getInsets ().left+ frame.getInsets ().right+320, frame.getInsets ().top+ frame.getInsets ().bottom+240); frame.show (); } } class MainFrameWnd extends Frame { MenuBar mbMainMenuBar; Menu mnFile; Menu mnHelp; boolean fDBEmpty=true; public MainFrameWnd (String sTitle) { super (sTitle); setSize (400, 200); setBackground (Color.yellow); setForeground (Color.black); setLayout (new FlowLayout ()); mbMainMenuBar=new MenuBar (); mnFile=new Menu ("File"); mnFile.add ("New... "); mnFile.add ("View records... "); mnFile.add ("-)"; mnFile.add ("Exit"); mnHelp=new Menu ("Help"); mnHelp.add ("Content"); mnHelp.add ("-)"; mnHelp.add ("About"); mbMainMenuBar.add (mnFile); mbMainMenuBar.add (mnHelp); setMenuBar (mbMainMenuBar); } public void paint (Graphics g) { g.setFont (new Font ("Helvetica", Font.PLAIN, 12)); g.drawString ("Frame window", 10, 70); super.paint (g); } public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) { setVisible (false); System.exit (0); return true; } else return super.handleEvent (evt); } public boolean action (Event evt, Object obj) { MenuItem mnItem; if (evt.target instanceof MenuItem) { mnItem=(MenuItem) evt.target; if (obj.equals ("Exit")) { System.exit (0); } else if (obj.equals ("New... ")) { if (fDBEmpty) { SimpleDBMS db=new SimpleDBMS ( "dbtest.idx", "dbtest.dat"); db.AddRecord ("Ivanov", 1000); db.AddRecord ("Petrov", 2000); db.AddRecord ("Sidoroff", 3000); db.close (); fDBEmpty=false; MessageBox mbox; mbox=new MessageBox ( "Database created", this, "Information", true); mbox.show (); } } else if (obj.equals ("View records... ")) { SimpleDBMS db=new SimpleDBMS ( "dbtest.idx", "dbtest.dat"); String szRecords; szRecords= db.GetRecordByNumber (0)+ db.GetRecordByNumber (1)+ db.GetRecordByNumber (2); db.close (); MessageBox mbox; mbox=new MessageBox (szRecords, this, "Database records", true); mbox.show (); } else if (obj.equals ("Content")) { MessageBox mbox; mbox=new MessageBox ( "Item Content selected", this, "Dialog from Frame", true); mbox.show (); } 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; } } class MessageBox extends Dialog { Label lbMsg; Button btnOK; public MessageBox (String sMsg, Frame parent, String sTitle, boolean modal) { super (parent, sTitle, modal); resize (300, 100); setLayout (new GridLayout (2, 1)); lbMsg=new Label (sMsg, Label.CENTER); add (lbMsg); btnOK=new Button ("OK"); add (btnOK); } public boolean handleEvent (Event evt) { if (evt.id == Event.WINDOW_DESTROY) { dispose (); return true; } else return super.handleEvent (evt); } public boolean action (Event evt, Object obj) { Button btn; if (evt.target instanceof Button) { btn=(Button) evt.target; if (evt.target.equals (btnOK)) { dispose (); } else return false; return true; } return false; } } class SimpleDBMS { RandomAccessFile idx; RandomAccessFile dat; long idxFilePointer=0; 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 ()); } } public void close () { try { idx.close (); dat.close (); } catch (Exception ioe) { System.out.println (ioe.toString ()); } } 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 ()); } } public String GetRecordByNumber (long nRec) { String sRecord="<empty>"; try { Integer account; 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; } } 1.11.13.2. Опис текст-джерела прикладної програми DirectFileДля роботи з базою даних ми створили клас SimpleDBMS, визначивши в ньому розробник, методи для додавання записів, добування записів по їхньому порядковому номеру, а також метод для завершення бази даних. 1.11.13.2.1. Генерування бази даних Коли користувач вибирає з меню File рядок New, відповідний маніпулятор події створює базу даних, передаючи розробнику імена файлу вказівника 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); Після завершення роботи з базою даних вона закривається методом close з класу SimpleDBMS: db.close (); 1.11. 13.2. 2. Відображення інформації записів бази даних При виборі рядка View records з меню File прикладна програма відкриває файл бази даних: SimpleDBMS db=new SimpleDBMS ( "dbtest.idx", "dbtest.dat"); Після цього воно витягає три записи з номерами 0, 1 і 2, викликаючи для цього метод GetRecordByNumber, також визначений в класі SimpleDBMS: String szRecords; szRecords=db.GetRecordByNumber (0)+ db.GetRecordByNumber (1)+ db.GetRecordByNumber (2); Записи об'єднуються та зберігаються в змінній szRecords типу String. Після цього база даних закривається: db.close (); Для відображення вмісту записів ми створюємо діалогову панель на базі визначеного нами класу MessageBox: MessageBox mbox; mbox=new MessageBox (szRecords, this, "Database records", true); mbox.show (); 1.11.13.2.3. Клас SimpleDBMS Розглянемо тепер клас SimpleDBMS. В цьому класі визначене три поля з іменами idx, dat та idxFilePointer, а також три методи. Поля idx та dat є об'єктами класу RandomAccessFile та являють собою, відповідно, посилання на файл вказівника й файл даних. Поле idxFilePointer типу long використовується для вилучення і запам'ятовує поточне зміщення в файлі. Розробник класу SimpleDBMS має вигляд достатньо просто. Все, що він робить, - це створює два об'єкту класу RandomAccessFile, відповідно, для вказівника та даних: idx=new RandomAccessFile (IndexFile, "rw"); dat=new RandomAccessFile (DataFile, "rw"); Таким чином як у вигляді другого параметра розробнику класу RandomAccessFile передається рядок "rw", файли відкриваються й для читання, й для запису. 1.11.13.2.4. Метод close Метод close закриває файли вказівника та даних, викликаючи метод close з класу RandomAccessFile: idx.close (); dat.close (); 1.11.13.2.5. Метод 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); Зверніть увагу, що до рядка ми додаємо символи повернення каретки та зміни рядка. Це зроблене винятково для того щоб означити кінець рядка літерно-цифрового поля. 1.11.13.2.6. Метод 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 вертає у вигляді рядка , що витягнутий з бази даних.
1.12. СТВОРЕННЯ МЕРЕЖЕВИХ ПРИКЛАДНИХ ПРОГРАМ
Коли ми починали розмову про мову програмування Java, то позначали, що вона спеціально орієнтований на глобальні мережі, такі як Internet. В цьому розділі ми почнемо знайомство з конкретними класами Java, розробленими для мережевого програмування. На прикладі нашої прикладної програми ви зможите переконатися, що класи Java дійсно дуже зручні для генерування мережевої прикладної програми. В цьому розділі ми розглянемо два аспекту мережевого програмування. Перший з них стосується доступу з прикладної програми Java до файлів, розташованих на сервері Web, другий - генерування серверної та клієнтської прикладних програм з використанням гнізд (сокетів) . Нагадаємо, що з міркувань безпеки аплетам повністю вимкнений доступ до локальних файлів робочої станції, під'єднаної до мережі. Тим не менш, аплет може працювати з файлами, розташованими на серверах Web. При цьому можна використовувати вхідні та вихідні трафіки, описані нами в попередньому розділі. Для чого аплетам звертатися до файлів сервера Web? Таким аплетам можна знайти множину застосувань. Уявіть собі, наприклад, що вам потрібне відображати у користувача графік, вхідні дані для побудови якого постійно перебують на сервері Web. Цю задачу можна розв'язати, грубо говорячи, двома способами. Перший полягає в тому, що ви створюєте розширення сервера Web у вигляді прикладної програми CGI або ISAPI, що на основі вхідних даних динамічно компонує графічне зображення бланку у вигляді файлу GIF та посилає його користувачу. Однак на шляху розв'язування задачі за допомогою розширення сервера Web вас очікують дві неприємності. По-перше, створити з програми вродливий пофарбований графічний файл в стандарті GIF не таким чином-то просто - ви повинні розібратися з форматом цього файлу та створити всі необхідні заголовки. По-друге, графічний файл займає багато місця й передається по каналах Internet достатньо поволі - середня швидкість передавання даних в Internet складає 1 Кбайт в секунду (для деяких районів Києва це дуже гарний показник). В той же час файл з вхідними даними може бути дуже компактним. Виникає запит - чи не можна передавати через Internet тільки вхідні дані, а побудова графіка виконувати на робочій станції користувача? В цьому полягає другий спосіб, що припускає застосування аплетів. Ваша прикладна програма може, наприклад, одержувати через мережу файл вхідних даних, а після цього на основі змісту цього файлу малювати в свойому вікні пофарбовану кругову діаграму. Кількість даних , що передаються при цьому у порівнянні з використанням розширення сервера Web скорочується в десятки та сотні раз. Окрім роботи з файлами, розташованими на сервері Web, ми розповімо про генерування каналів між прикладною програмою Java, працюючою на різних комп'ютерах в мережі, з використанням гнізд. Гнізда (сокети) дозволяють організувати тісну взаємодію аплетів і повноцінної прикладної програми Java, при цьому аплети можуть передавати друг другу дані крізь мережу Internet. Це відкриває широкі можливості для опрацювання інформації по схемі клієнт-сервер, причому в ролі серверів тут може виступати будь-який комп'ютер, під'єднаний до мережі, а не тільки сервер Web. Кожна робоча станція може виступати одночасно й в ролі сервера, й в ролі клієнта. 1.12.1. Адреса IP та клас InetAddressПерш ніж починати генерування мережевої прикладної програми для Internet, ви повинні розібратися з адресуванням комп'ютерів в мережі з протоколом TCP/IP, на базі якого побудована мережа Internet. Тут ми наведемо самі необхідні для цього дані. Всі комп'ютери, під'єднані до мережі TCP/IP, називаються вузлами (в первісній термінології вузол - це host). Кожний вузол має в мережі свою адресу IP, яка складається з чотирьох десяткових цифр в діапазоні від 0 до 255, з’єднаних літерою "крапка", наприклад: 193.120.54.200 Фактично адреса IP буде 32-розрядним двійковим числом. Згадані числа являють собою окремі байти адреси IP. Оскільки працювати з цифрами зручно лише комп'ютеру, була вигадана система доменів імен. При використанні цієї системи адресам IP ставиться у відповідність таким чином домен, який називається адреса, така як, наприклад, www. sun. 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); 1.12.1.1. Розглянемо застосування цих методів.Щоб працювати з адресами IP, перш за все ви повинні створити об'єкт класу InetAddress. Ця процедура виконується не за допомогою оператора new, а з застосуванням статичних методів getLocalHost, getByName та getAllByName. 1.12.1.2. Генерування об'єкту класу InetAddress для локального блокуМетод getLocalHost створює об'єкт класу InetAddress для локального блоку, тобто для тієї робочої станції, на якій виконується прикладна програма Java. Таким чином як цей метод статичний, ви можете викликати його, посилаючись на ім'я класу InetAddress: InetAddress iaLocal; iaLocal=InetAddress.getLocalHost (); 1.12.1.3. Генерування об'єкту класу InetAddress для віддаленого блокуВ тому разі, якщо вас цікавить віддалений вузол мережі Internet або корпоративної мережі Intranet, ви можете створити для нього об'єкт класу InetAddress за допомогою методів getByName або getAllByName. Перший з них вертає адресу вузла, а другий - масив всіх адрес IP, спарованих з даним вузлом. Якщо вузол з означеним ім'ям не існує, при виконанні методів getByName та getAllByName виникає вилучення UnknownHostException. Помітимо, що методам getByName та getAllByName можна передавати не тільки ім'я блоку, таке як, наприклад, "sun.com", але й рядок адреси IP у вигляді чотирьох десяткових чисел, поєднаних крапками. Після генерування об'єкту класу InetAddress для локального або віддаленого вузла ви можете використовувати інші методи цього класу. 1.12.1.4. Визначення адреси IPМетод getAddress вертає масив з чотирьох байтів адреси IP об'єкту. Байт з нульовим індексом цього масиву містить старший байт адреси IP. Метод toString повертає текстовий рядок, що містить ім'я вузла, розділовий знак'/' та адресу IP у вигляді чотирьох десяткових чисел, поєднаних крапками. 1.12.1.5. Визначення імені вузлаЗа допомогою методу getHostName ви можете визначити ім'я вузла, для якого був створений об'єкт класу InetAddress. 1.12.1.6. Порівнювання адрес IPТа, нарешті, метод equals передвизначений для порівнювання адрес IP як об'єктів класу InetAddress. 1.12.2. Універсальна адреса ресурсів 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.sun.com http://157.23.12.101 Необов'язковий параметр port специфікує номер багаторозрядного входу для роботи з сервером. За замовчанням для журналу HTTP використовується багаторозрядний вхід з номером 80, однак для спеціалізованих серверів Web це може бути й не так. Номер багаторозрядного входу ідентифікує програму, працюючу у вузлі мережі TCP/IP та взаємодіючу з іншими програмами, розташованими на тому же або на другом вузлі мережі. Якщо ви розробляєте програму, що передає дані через мережу TCP/IP з використанням, наприклад, інтерфейсу гнізд Windows Sockets, то при генеруванні з'єднання з приділеним комп'ютером ви повинні вказати не тільки адресу IP, але й номер багаторозрядного входу, що буде використаний для передавання даних. Нижче ми показали, як потрібне вказувати в адресі URL номер багаторозрядного входу: http://www.myspecial.srv/: 82 Тепер займемося параметром path, що визначить маршрут до об'єкту. Звичайно будь-який сервер Web або FTP має кореневий каталог, в якому розміщені підкаталог. Як у кореневому каталозі, так й у підкаталозі сервера Web можуть постійно перебувати документи HTML, файли двійкових кодів, файли з графічними зображеннями, звукові та відео-файли, розширенння сервера у вигляді програм CGI або бібліотек динамічного укладання, що доповнюють можливості сервера. Якщо у вигляді адреси URL вказати вікну перегляду тільки домен-ім'я сервера, сервер пересилає вікну перегляду свою головну сторінку. Ім'я файлу цієї сторінки залежить від сервера. Більшість серверів на базі операційної системи UNIX посилають за замовчанням файл документа з ім'ям index.html. Інші сервери Web можуть використовувати для цієї мети ім'я default.htm або будь-що ще, визначене при встановленні сервера, наприклад, home.html або home.htm. Для звертання на конкретний документ HTML або на файл будь-якого іншого об'єкту необхідно вказати в адресі URL його шлях, що вмикає ім'я файлу, наприклад: http://www.glasnet.ru/~frolov/index.html http://www.dials.ccas. ru/frolov/home.htm Кореневий каталог сервера Web позначається знаком /. В специфікації протоколу HTTP сказане, що якщо шлях не специфікований, то використовується кореневий каталог. 1.12.3. Клас URL в бібліотеці класів JavaДля роботи з ресурсами, специфікованими своїми адресами URL, в бібліотеці класів Java є дуже зручний та потужний клас з назвою URL. Простота генерування мережевої прикладної програми з використанням цього класу в значній мірі спростовує загальнопоширене переконання у складності мережевого програмування. Інкапсулюючи в собі достатньо складні процедури, клас URL надає в розпорядження програміста невеликий комплект простих у використанні розробників та методів. 1.12.3.1. Розробники класу 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, такі як протокол, ім'я вузлу, файлу або номер багаторозрядного входу. 1.12.3.2. Методи класу URLРозглянемо самі цікаві методи, визначені у класі URL. 1.12.3.2.1. Метод openStream Метод openStream дозволяє створити вхідний потік для читання файлу ресурсу, спарованого з створеним об'єктом класу URL: public final InputStream openStream (); Для виконання операції читання з створеного таким чином трафіка ви можете використовувати метод read, визначений в класі InputStream (будь-яку з його присмаку). Дану пару методів (openStream з класу URL і read з класу InputStream) можна застосувати для розв'язування вітки одержання вмісту бінарного або текстового файлу, що запам'ятовується в одному з директорій сервера Web. Зробивши це, звичайна прикладна програма Java або аплет може виконати локальне оброблення отриманого файлу на комп'ютері віддаленого користувача. 1.12.3.2 2. Метод getContent Дуже цікавий метод getConten. Цей метод визначає та одержує зміст мережевого ресурсу, для якого створений об'єкт URL: public final Object getContent (); Практично ви можете використовувати метод getContent для одержання текстових файлів, розташованих в мережевих директоріях. На жаль, даний метод непридатний для одержання документів HTML, оскільки для даного ресурсу не визначений оброблювач інформації, призначений для генерування об'єкту. Метод getContent не здатний створити об'єкт ані з чого іншого, окрім текстового файлу. Дана проблема, тим не менш, розв'язується дуже просто - достатньо замість методу getContent використовувати описану вище комбінацію методів openStream з класу URL та read з класу InputStream. 1.12.3.2.3. Метод getHost За допомогою методу getHost ви можете визначити ім'я вузлу, відповідного даному об'єкту URL: public String getHost (); 1.12.3.2.4. Метод getFile Метод getFile дозволяє отримати дані про файл, спарований з даним об'єктом URL: public String getFile (); 1.12.3.2.5. Метод getPort Метод getPortt передвизначений для визначення номеру багаторозрядного входу, на якому виконується взаємодія для об'єкту URL: public int getPort (); 1.12.3.2.6. Метод getProtocol За допомогою методу getProtocol ви можете визначити протокол, з застосуванням якого установиться посилання на ресурс, специфікований об'єктом URL: public String getProtocol (); 1.12.3.2.7. Метод getRef Метод getRef вертає текстовий рядок звертання на ресурс, відповідний даному об'єкту URL: public String getRef (); 1.12.3.2.8. Метод hashCode Метод hashCode вертає геш-код об'єкту URL: public int hashCode (); 1.12.3.2.9. Метод sameFile За допомогою методу sameFile ви можете визначити, посилаються чи два об'єкту класу URL на один й той же ресурс, або ні: public boolean sameFile (URL other); Якщо об'єкти посилаються на один й той же ресурс, метод sameFile вертає значення true, якщо ні - false. 1.12.3.2.10. Метод equals Ви можете використовувати метод equals для визначення тотожності адрес URL, специфікованих двома об'єктами класу URL: public boolean equals (Object obj); Якщо адреси URL однакові, метод equals вертає значення true, якщо немає - значення false. 1.12.3.2.11. Метод toExternalForm Метод toExternalForm вертає текстовий рядок зовнішнього виразу адреси URL, визначеної даним об'єктом класу URL: public String toExternalForm (); 1.12.3.2.12. Метод toString Метод toString вертає текстовий рядок, відповідний даному об'єкту класу URL: public String toString (); 1.12.3.2.13. Метод openConnection Метод openConnection передвизначений для генерування діалога між прикладною програмою та мережевим ресурсом, наведеним об'єктом класу URL: public URLConnection openConnection (); Якщо ви створюєте прикладну програму, що дозволяє читати з директорій сервера Web літерно-цифрові або файли двійкових кодів, можна створити трафік методом openStream або отримати зміст літерно-цифрового ресурсу методом getContent. Однак є й інша можливість. Спочатку ви можете створити діалог, як об'єкт класу URLConnection, викликавши метод openConnection, а після цього створити для цього діалога вхідний трафік, скористувавшись методом getInputStream, визначеним в класі URLConnection. Така процедура дозволяє визначити або встановити перед генеруванням трафіка деякі властивості діалога, наприклад, специфікувати занесення в геш-пам'ять. Однак сама цікава можливість, яку надає цей метод, полягає в організації взаємодії прикладної програми Java та сервера Web. 1.12.4. Обмін даними з використанням гніздВ бібліотеці класів Java є дуже зручний засіб, за допомогою яких можна організувати взаємодію між прикладною програмою Java та аплетами, працюючими як на одному й тому же, так й на різних вузлах мережі TCP/IP. Цей засіб, народжений в світі операційної системи UNIX, зветься гнізда (sockets). Що таке гнізда? Ви можете уявити собі гніздо у вигляді двох розеток, в які ввімкнутий кабель, призначений для передавання даних через мережу. Переходячи до комп'ютерної термінології, скажемо, що гнізда - це програмний інтерфейс, призначений для передавання даних між прикладними програмами. Перш ніж прикладна програма зможе виконувати передавання або одержання даних, вона повинно створити гніздо, вказавши при цьому адресу вузла IP, номер багаторозрядного входу, через який будуть передаватися дані, та тип гнізда. З адресою вузла IP ви вже зштовхувались. Номер багаторозрядного входу заступає для розпізнавання прикладної програми. Помітимо, що існують так "добре відомі' (well known) номери , що називаються багаторозрядними входами, зарезервовані для різних прикладних програм. Наприклад, багаторозрядний вхід з номером 80 зарезервований для використання серверами Web при обміні даними через протокол HTTP. Що до типів гнізд, то їх два - потокові та датаграмні. За допомогою потокових гнізд ви можете створювати канали передавання даних між двома прикладними програмами Java у вигляді трафіків, що ми вже розглядали в попередньому розділу. Трафіки можуть бути вхідними або об'єктними, звичайними або форматованими, з використанням або без використання буферизації. Скоро ви переконаєтесь, що організувати обмін даними між прикладною програмою Java з використанням потокових гнізд не тяжче, ніж працювати через трафіки з звичайними файлами. Помітимо, що потокові гнізда дозволяють передавати дані тільки між двома прикладними програмами, оскільки вони припускають генерування діалога між цими прикладними програмами. Однак іноді потрібне забезпечити взаємодію декількох клієнтських прикладних програм з одним серверним або декількох клієнтських прикладних програм з декількома серверними прикладними програмами. В цьому випадку ви можете або створювати в серверній прикладній програмі окремі завдання та окремі канали для кожної клієнтської прикладної програми, або скористуватися датаграмними гніздами. Останні дозволяють передавати дані відразу всім вузлам мережі, хоча така можливість рідко використовується й часто блокується адміністраторами мережі. Для передавання даних через датаграмні гнізда вам не потрібне створювати діалог - дані посилаються безпосередньо тій прикладній програмі, для якої вони передвизначені з застосуванням адреси цієї прикладної програми у вигляді гнізда та номери багаторозрядного входу. При цьому одна клієнтська прикладна програма може обмінюватися даними з декількома серверними прикладними програмами або навпаки, одна серверна прикладна програма - з декількома клієнтськими. На жаль, датаграмні гнізда не гарантують доставлення кластерів даних, що передаються. Навіть якщо кластери даних, що передаються через такі гнізда, дійшли до адресата, не гарантується, що вони будуть отримані в тій же самій послідовності, в якій були передані. Потокові гнізда, навпроти, гарантують доставлення кластерів даних, причому в правильній послідовності. Причина відсутності гарантії передачі даних при використанні датаграмних гнізд полягає у застосуванні такими гніздами протоколу UDP, що, в свою чергу, започаткований на протоколі з негарантованою передачею IP. Потокові гнізда працюють через протокол гарантованої передачі TCP. 1.12.5. Робота з потоковими гніздамиЯк ми вже говорили, інтерфейс гнізд дозволяє передавати дані між двома прикладними програмами, працюючими на одному або різних вузлах мережі. В процедурі генерування каналу передавання даних одна з цієї прикладної програми виконує роль сервера, а інша - роль клієнта. Після того як канал буде створений, прикладні програми стають рівноправними - вони може передавати друга другу дані симетричним чином. Розглянемо цю процедуру в дрібницях. 1.12.5.1. Присвоювання початкових значень сервераСпочатку ми розглянемо операції прикладної програми, що на час присвоювання початкових значень буде сервером. Перше, що повинно зробити серверна прикладна програма, це створити об'єкт класу 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, що ми розглянемо пізніше. 1.12.5.2. Присвоювання початкових значень замовникаПроцедура присвоювання початкових значень клієнтської прикладної програми має вигляд надто просто. Клієнт повинен просто створити гніздо як об'єкт класу Socket, вказавши адреса IP серверної прикладної програми та номер багаторозрядного входу, що використовується сервером: Socket s; s=new Socket ("localhost", 9999); Тут у вигляді адреси IP ми вказали спеціальну адресу localhost, призначену для тестування мережевої прикладної програми, а у вигляді номеру багаторозрядного входу - значення 9999, що використовувалося сервером. Тепер можна створювати вхідний та вихідній трафіки. На стороні замовника ця операція виконується точно також, як й на стороні сервера: InputStream is; OutputStream os; is=s.getInputStream (); os=s.getOutputStream (); 1.12.5.3. Обмін даними між клієнтом та серверомПісля того як серверна та клієнтська прикладні програми створили трафіки для приймання та обміну даними, обидві ці прикладні програми можуть читати та писати в канал даних, викликаючи методи 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 ви можете створити трафіки , що довантажувалися, та трафіки для передавання структурованих даних, про які ми розповідали раніше. 1.12.5.4. Завершення роботи сервера та клієнтаПісля завершення обміну даними ви повинні закрити трафіки, викликавши метод close: is.close (); os.close (); Коли канал передавання даних більше не потрібний, сервер та клієнт повинні закрити гніздо, викликавши метод close, визначений у класі Socket: s.close (); Серверна прикладна програма, крім того, повинно закрити з'єднування, викликавши метод close для об'єкту класу ServerSocket: ss.close (); 1.12.6. Клас SocketПісля стислого введення у гнізда наведемо опис найбільш цікавих розробників та методів класу Socket. 1.12.6.1. Розробники класу 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. 1.12.6.2. Методи класу 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 (); 1.12.7. Використання датаграмних гніздЯк ми вже говорили, датаграмні гнізда не гарантують передачу кластерів даних. Тим не менш, вони працюють швидше потокових та забезпечують можливість широкомовного розсилання кластерів даних одночасно всім вузлам мережі. Остання можливість використовується не дуже широко в мережі Internet, однак в корпоративній мережі Intranet ви цілком можете їю скористуватися. Для роботи з датаграмними гніздами прикладна програма повинна створити гніздо на базі класу DatagramSocket, а також підготувати об'єкт класу DatagramPacket, в який буде записаний прийнятий від партнера по мережі елемент даних. Канал, а також вхідні та вихідні трафіки створювати не потрібне. Дані передаються та вводяться з клавіатури методами send й receive, визначеними в класі DatagramSocket. 1.12.7.1. Клас 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 виконується тільки для об'єктів, розташованих в оперативній пам'яті. Такі об'єкти, як трафіки та гнізда, ви повинні закривати після використання самостійно. 1.12.7.2. Клас 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 адресу мережі. Така процедура звичайно використовується в локальних мережах. 1.12.7.3. Як вказати адресу мережі?Нагадаємо, що адреса IP складається з двох частин - адреси мережі та адреси вузла. Для використання розширення 32-розрядної адреси IP використовується 32-розрядна комбінація розрядів, в якій бітам адреси мережі узгоджують одиниці, а бітам адреси блоку - нулі. Наприклад, адреса блоку може бути вказана як 193.24.111.2. Виходячи з значення старшого байту адреси, це мережа класу С, для якої за замовчанням використовується комбінація розрядів 255.255.255.0. Отже, адреса мережі буде такою: 193.24.111.0. 1.12.8. Взаємодія прикладної програми Java з розширенннями сервера WebОтже, ми розповіли вам, як прикладні програми Java може одержувати з сервера Web для оброблення довільні файли, а також як вони можуть передавати дані друг другу з застосуванням потокових або датаграмних гнізд. Однак найбільш вражаючі можливості відкриваються, якщо організувати взаємодію між прикладною програмою Java та розширенням сервера Web, таким як CGI або ISAPI. В цьому випадку прикладна програма або аплети Java могли би посилати довільні дані розширенню сервера Web для оброблення, а після цього одержувати результат цього оброблення у вигляді файлу. 1.12.8.1. Взаємодія прикладної програми Java та розширення сервера WebПроцедура організації взаємодії прикладної програми Java та розширень сервера Web заснована на застосуванні класів URL й URLConnection. Прикладна програма Java, що бажає працювати з розширенням сервера Web, створює об'єкт класу URL для програми розширення (тобто для виконуваного модуля розширення CGI або бібліотеки динамічного укладання DLL розширення ISAPI). Далі прикладна програма одержує звертання на канал передавання даних з цим розширенням як об'єктом класу URLConnection. Після цього, користуючись методами getOutputStream та getInputStream з класу URLConnection, прикладна програма створює з розширенням сервера Web об'єктний та вхідний канал обміну даними. Коли дані передаються прикладною програмою у вихідний канал, створений подібним чином, він влучає в стандартний трафік введення прикладної програми CGI, немов би дані прийшли методом POST з форми, визначеної в документі HTML. Обробивши отримані дані, розширення CGI записує їх в свій стандартний вихідний трафік, після чого ці дані стають досяжним прикладній програмі Java через вхідний трафік, відкритий методом getInputStream класу URLConnection. На мал. 46 показані трафік даних для описаної вище схеми взаємодії прикладної програми Java та розширення сервера Web з інтерфейсом CGI.
Мал. 46. Взаємодія прикладної програми Java з розширенням сервера Web на базі інтерфейсу CGI Розширенння ISAPI працюють аналогічно, однак вони одержують дані не зі стандартного вхідного трафіку, а за допомогою виклику спеціально призначеної для цього функції інтерфейсу ISAPI. Замість стандартного трафіка виведення також застосовується спеціальна функція. 1.12.8.2. Клас 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 (); Перед налагоджуванням цього зв’язку прикладна програма може встановити різні параметри переходу. Деякі з методів, призначених для цього, наведені нижче:
В класі URLConnection є методи, що дозволять визначити значення параметрів, встановлених щойно описаними методами: public boolean getDefaultUseCaches (); public boolean getUseCaches (); public boolean getDoInput (); public boolean getDoOutput (); public long getIfModifiedSince (); Визначений інтерес можуть представляти методи, призначені для добування даних із рубрик протоколу HTTP:
Інші методи, визначені в класі URLConnection, дозволяють отримати всіх заголовки або заголовки з специфікованим номером, а також інші дані про з'єднування. У разі необхідності ви знайдете опис цих методів у довідковій системі Java WorkShop. 1.12.9. Аплет ShowChartСпробуємо тепер на практиці застосувати метод передавання файлів з директорії сервера Web до аплета для локального оброблення. Наша наступна прикладна програма з назвою ShowChart одержує невеликий текстовий файл з вхідними даними для побудови кругової діаграми, зміст якого представлений нижче: 10.20, 5.35, 11.10, 3.6, 80.10, 20.5, 35.11, 10.3, 6.80 В цьому файлі постійно перебують обчислювальні значення кутів для окремих секторів діаграми, причому сума цих значень рівна 360 градусам. Наш аплет вводить з клавіатури цей файл через мережу та малює кругову діаграму, показану на мал. 47.
Мал. 47. Кругова діаграма, побудована на базі вхідних даних, отриманих через мережу Файл вхідних даних займає усього 49 байт, тому він передається по мережі дуже швидко. Якби ми передавали графічне зображення цієї діаграми, статично або динамічно, підготоване, наприклад, розширенням сервера CGI або ISAPI, обсяг даних , що передаються по мережі, була б набагато більше (наприклад файл малюнка 47 має обсяг - 3,82 Кбайт) . 1.12.9.1. Текст-джерела аплету ShowChartТекст-джерело прикладної програми ShowChart наведене в роздруку нижче. /*Файл ShowChart.java*\ import java.applet.; import java.awt.; import java.net.; import java.io.; import java.util.; public class ShowChart extends Applet { URL SrcURL; Object URLContent; int errno=0; String str; byte buf []=new byte [200]; public String getAppletInfo () { return "Name: ShowChart"; } public void init () { try { SrcURL=new URL ( "http://frolov/chart.txt"); try { InputStream is=SrcURL.openStream (); is.read (buf); str=new String (buf, 0); } catch (IOException ioe) { showStatus ("read exception"); errno=1; } } catch (MalformedURLException uex) { showStatus ( "MalformedURLException exception"); errno=2; } } public void paint (Graphics g) { Integer AngleFromChart=new Integer (0); int PrevAngle=0; int rColor, gColor, bColor; Dimension dimAppWndDimension=getSize (); 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); showStatus (str); StringTokenizer st= new StringTokenizer (str,",\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, створеного автоматично для нашого аплету, представлене в роздруку нижче. /*Файл ShowChart.tmp.html*\ <applet name="ShowChart" code="ShowChart" codebase= "file:/e:/Sun/Articles/vol12/src/ShowChart" width="200" height="200" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> <hr>If your browser recognized the applet tag, you would see an applet here. <hr> </applet> 1.12.9.2. Опис текст-джерела аплету ShowChartАплет ShowChart одержує зміст файлу вхідних даних для конструкції кругової діаграми за допомогою класу URL. Як ви побачите, для одержання змісту цього файлу воно створює трафік введення явним чином. 1.12.9.2.1. Поля класу ShowChart В класі ShowChart визначені п'ять полів. URL SrcURL; Object URLContent; int errno=0; String str; byte buf []=new byte [200]; Поле SrcURL класу URL запам'ятовує адресу URL файлу вхідних даних для кругової діаграми. В поле URLContent типу Object буде перезаписаний зміст цього файлу. В поле errno запам'ятовується поточний код помилки, якщо вона виникла, або нульове значення, якщо всі операції були виконані без помилок. Поле str запам'ятовує прийнятий рядок, що заздалегідь записується в тимчасовий буфер buf. 1.12.9.2.2. Метод init Під час присвоювання початкових значень метод init створює об'єкт класу URL для файлу вхідних даних: SrcURL=new URL ("http://frolov/chart.txt"); Тут для спрощення текст-джерела ми вказали адресу URL файлу даних безпосередньо в програмі, однак ви можете передати цю адресу аплету через параметр в документі HTML. Далі для нашого об'єкту URL ми створюємо трафік введення та одержуємо зміст файлу (тобто вхідні дані для конструкції бланку): InputStream is=SrcURL.openStream (); is.read (buf); Прийняті дані записуються в буфер buf та після цього перетворюються до типу String за допомогою відповідного розробника: str=new String (buf, 0); Якщо при генеруванні об'єкту класу URL виникло вилучення, метод init записує в поле errno код помилки, рівний 2, записуючи при цьому в рядок стану браузера повідомлення "MalformedURLException exception". В тому разі, коли об'єкт класу URL створений успішно, а вилучення виникло в процедурі читання змісту файлу, в поле errno записується значення 1, а в рядок стану браузера - повідомлення "read exception". 1.12. 9.2. 3. Метод paint Після пофабування фону вікна аплету та малювання навколо нього рамки метод paint приступає до конструкції кругової діаграми. Прийняті дані відображаються в рядку стани браузера: 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)); За допомогою методу nextElement ми одержуємо чергове значення кута сектора і зберігаємо його в змінній angle: String angle=(String) st.nextElement (); Далі за допомогою розробника класу Integer це значення перетвориться в числове: AngleFromChart=new Integer (angle); Малювання сектора кругової діаграми виконується за допомогою методу fillArc: g.fillArc (0, 0, 200, 200, PrevAngle, AngleFromChart.intValue ()); У вигляді початкового значення кута сектора використовується значення з змінної PrevAngle. Відразу після присвоювання початкових значень в цю змінну записується нульове значення. Об'єктний кут сектора специфікується як AngleFromChart.intValue (), тобто вказується значення, отримане з прийнятого по мережі файлу вхідних даних. Після завершення малювання чергового сектора кругової діаграми початкове значення PrevAngle збільшується на величину кута мальованого сектора: PrevAngle += AngleFromChart.intValue (); 1.12.10. Прикладна програма SocketServ та SocketClientУ вигляді прикладу ми наведемо текст-джерела двох прикладних програм Java, працюючих з потоковими гніздами. Одне з цієї прикладної програми називається SocketServ та виконує роль сервера, друге називається SocketClient та заступає клієнтом. Прикладна програма SocketServ виводить на консоль рядок "Socket Server Application" й після цього переходить в стан чекання поєднання з клієнтською прикладною програмою SocketClient. Прикладна програма SocketClient встановлює з'єднування з сервером SocketServ, використовуючи потокове гніздо з номером 9999 (цей номер вибраний нами довільно). Далі клієнтська прикладна програма виводить на свій консоль запрошення для введення рядків. Введені рядки відображаються на консолі та передаються серверній прикладній програмі. Сервер, отримавши рядок, відображає її в свойому вікні та посилає зворотно замовнику. Замовник виводить отриманий від сервера рядок на консолі. Коли користувач вводить рядок "quit", цикл введення та передавання рядків завершується. Вся процедура показана на мал. 48 та 49.
Мал. 48. Вікно клієнтської прикладної програми
Мал. 49. Вікно серверної прикладної програми Тут в вікні клієнтської прикладної програми ми ввели декілька рядків, причому останній рядок був рядком "quit", який завершив роботу прикладної програми. 1.12.10.1. Текст-джерело серверної прикладної програми SocketServТекст-джерело серверної прикладної програми SocketServ наведене в роздруку нижче. /*Файл SocketServ.java*\ import java.io.; import java.net.; import java.util.; public class SocketServ { public static void main (String args []) { byte bKbdInput []=new byte [256]; ServerSocket ss; Socket s; InputStream is; OutputStream os; try { System.out.println ( "Socket Server Application"); } catch (Exception ioe) { System.out.println (ioe.toString ()); } try { 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 <Enter> to terminate application... "); System.in.read (bKbdInput); } catch (Exception ioe) { System.out.println (ioe.toString ()); } } } 1.12.10.2. Опис текст-джерела серверної прикладної програми 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 (); 1.12.10.3. Текст-джерело клієнтської прикладної програми SocketClientТекст-джерело клієнтської прикладної програми SocketClient наведене в роздруку нижче. /*Файл SocketClient.java*\ import java.io.; import java.net.; import java.util.; public class SocketClient { 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) { str=new String (bKbdInput, 0); StringTokenizer st; st=new StringTokenizer ( str,"\r\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,"\r\n)"; str=new String ( (String) st.nextElement ()); System.out.println (">>"+str); if (str.equals ("quit")) break; } } is.close (); os.close (); s.close (); } catch (Exception ioe) { System.out.println (ioe.toString ()); } try { System.out.println ( "Press <Enter> to"+ "terminate application... "); System.in.read (bKbdInput); } catch (Exception ioe) { System.out.println (ioe.toString ()); } } } 1.12.10.4. Опис текст-джерела клієнтської прикладної програми 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. Далі якщо користувач ввів рядок, а не просто натиснув на клавішу <Enter>, цей рядок відображається на консолі та передається серверу: 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 (); 1.12.11. Аплет FormНа прикладі аплету Form ми показати, як прикладна програма Java може взаємодіяти розширенннями сервера Web, такими як програми CGI або прикладна програма ISAPI. У вікні нашого аплету постійно перебує форма, що містить два однорядкових поля редагування, кнопку та багаторядкове поле редагування (мал. 50).
Мал. 50. Вікно аплету Form Ця форма призначена для додавання записів до бази даних, що містить електронні поштові адреси. Заповнивши поля ім'я та адреси E-Mail, користувач повинен натиснути кнопку Send. При цьому введені дані будуть передані розширенню сервера CGI, що запише їх в базу даних, а після цього відправить зворотньо аплету. Збережені записи, отримані від програми CGI, аплет FORM відобразить в багаторядковому полі редагування, як це показане на мал. 50. 1.12.11.1. Текст-джерела аплету FormТекст-джерела аплету Form представлені в роздруку нижче. /*Файл Form.java*\ import java.applet.; import java.awt.; import java.net.; import java.io.; import java.util.; public class Form extends Applet implements Runnable { private Thread m_store=null; TextField txtName; TextField txtEMail; TextArea txta; Button btnGetText; public void init () { Label lbName; Label lbEMail; Label lbPress; lbName=new Label ("Enter your name: "); lbEMail=new Label ( "Enter your E-Mail address: "); add (lbName); txtName=new TextField ("Your name", 40); add (txtName); add (lbEMail); txtEMail= new TextField ("your@email", 40); add (txtEMail); btnGetText=new Button ("Send! "); add (btnGetText); txta=new TextArea (8, 65); add (txta); setBackground (Color.yellow); } public void paint (Graphics g) { setBackground (Color.yellow); Dimension dimAppWndDimension=getSize (); g.setColor (Color.black); g.drawRect (0, 0, dimAppWndDimension.width - 1, dimAppWndDimension.height - 1); } public boolean action (Event evt, Object obj) { Button btn; if (evt.target instanceof Button) { btn=(Button) evt.target; if (evt.target.equals (btnGetText)) { startTransaction (); } else return false; return true; } return false; } void startTransaction () { m_store=new Thread (this); m_store.start (); } public void stop () { if (m_store!=null) { m_store.stop (); m_store=null; } } public void run () { URL u; URLConnection c; PrintStream ps; DataInputStream is; try { String szSourceStr= txtName.getText ()+ ","+txtEMail. getText (); String szReceived; String szURL= "http://frolov/scripts/store.exe"; u=new URL (szURL); c=u.openConnection (); ps=new PrintStream ( c.getOutputStream ()); ps.println (szSourceStr); ps.close (); is=new DataInputStream ( c.getInputStream ()); szReceived=is.readLine (); is.close (); txta.appendText (szReceived+"\r\n)"; repaint (); } catch (Exception ioe) { showStatus (ioe.toString ()); stop (); } } } Текст-джерело документа HTML, що було підготоване для вас системою Java Workshop, ми небагато відредагували, змінивши параметр CODEBASE /*Файл Form.tmp.html*\ <applet name="Form" code="Form.class" codebase="http://frolov/" width="500" height="200" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> <hr>If your browser recognized the applet tag, you would see an applet here. <hr> </applet> В цьому параметрі слідує вказати маршрут до директорії, в якій розміщується байт-код аплету. 1.12.11.2. Опис текст-джерел аплету FormПри присвоюванні початкових значень метод init створює всі необхідні органи управління та додає їх у вікно аплету. Коли користувач заповнює форму та натицькає кнопку Send, оброблювач відповідної події викликає метод startTransaction, процедуру, що виконує обмін даними з розширенням сервера Web: if (evt.target.equals (btnGetText)) { startTransaction (); } Метод startTransaction, визначений в нашій прикладній програмі, створює та запускає на виконання трафік, що й буде взаємодіяти програмою CGI: void startTransaction () { m_store=new Thread (this); m_store.start (); } При цьому у вигляді окремого трафіка, працюючого одночасно з кодом аплету, виступає метод run. Саме в ньому зосереджена вся логіка обміну даними з сервером Web. Таким чином як в процедурі взаємодії можуть виникати різні вилучення, ми передбачили їхнє оброблення за допомогою блоку try-catch: URL u; URLConnection c; PrintStream ps; DataInputStream is; try { ... } catch (Exception ioe) { showStatus (ioe.toString ()); stop (); } Назва виниклого вилучення буде відображена в рядку стану браузера. Тепер про те, що робить метод run після одержання управління. Першою справою він витягає з однорядкових літерно-цифрових полів ім'я та електронну адресу, об'єднуючи їх та записуючи отриманий текстовий рядок в поле szSourceStr: String szSourceStr= txtName.getText ()+","+ txtEMail.getText (); В рядку szURL постійно перебує адреса URL програми CGI: String szURL= "http://frolov/scripts/store.exe"; В реальній прикладній програмі цю адресу необхідно передавати аплету через параметр. Ми використовували безпосереднє кодування тільки для спрощення текст-джерела. На наступному етапі метод run створює для програми CGI об'єкт класу URL та відкриває з ним з'єднування: u=new URL (szURL); c=u.openConnection (); Користуючись цим електричним переходом, метод run створює форматований трафік виводу, записує в нього рядок ім'я та електронну адресу, а після цього закриває трафік: ps=new PrintStream (c.getOutputStream ()); ps.println (szSourceStr); ps.close (); Передані таким чином дані влучать в стандартний трафік введення програми CGI, звідки вона їх і прочитає. Зробивши це, програма CGI запише в стандартний вихідний трафік рядок відгуку, який необхідно прочитати в методі run нашого аплету. Для цього ми відкриваємо вхідний потік, створюємо на його основі форматований вхідний потік даних, читаємо один рядок тексту та закриваємо вхідний потік: is=new DataInputStream (c.getInputStream ()); String szReceived; szReceived=is.readLine (); is.close (); Відразу після цього програма CGI завершить свою роботу та буде готова до оброблення нових запитів на додавання записів. Що до методу run, то він додасть отриманий від розширення сервера текстовий рядок в багаторядкове вікно редагування, як це показане нижче, а після цього викличе перемалювання вікна аплету: txta.appendText (szReceived+"\r\n)"; repaint (); Помітимо, що спосіб обміну даними, який нами використовувався, підходить тільки для латинських знаків. Якщо вам потрібне передавати знаки кирилиці, слідує перетворювати їх з кодування UNICODE, наприклад, в гексадецимальне кодування, а в програмі CGI виконувати зворотне перетворення. Аналогічну процедуру можна застосовувати й для передавання довільних двійкових даних. 1.12.11.3. Текст-джерело програми CGI store.exeТекст-джерело програми CGI store.exe дуже простий й показаний в роздруку нижче. /*Файл store c*\ #include <windows.h> #include <tchar.h> #include <wchar.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void main (int argc, char *argv []) { int nInDatasize; char szMethod; char szBuf [2000]; FILE *fDatabase; CRITICAL_SECTION csAddRecord; szMethod=getenv ("REQUEST_METHOD"); if (! strcmp (szMethod, "POST")); { nInDatasize=atoi ( getenv ("CONTENT_LENGTH")); fread (szBuf, nInDatasize, 1, stdin); szBuf [nInDatasize]='\0'; InitializeCriticalSection (&csAddRecord); EnterCriticalSection (&csAddRecord); fDatabase= fopen ("c:\\EMAIL.DAT", "a+"); if (fDatabase!=NULL) { fputs (szBuf, fDatabase); fclose (fDatabase); } LeaveCriticalSection (&csAddRecord); DeleteCriticalSection (&csAddRecord); printf ( "Content-'type: text/plain\r\n\r\n)"; printf ("Stored information: %s", szBuf); } } Цей текст підготований для роботи в середовищі Windows 95 або Windows NT, таким чином як для синхронізації доступу до файлу ми використовували специфічні для цих операційних систем функції роботи з критичними ділянками. Свою роботу програма CGI починає з аналізу змінної режиму REQUEST_METHOD. Впевнуючись, що при запускі програми їй передали дані методом POST, програма визначає розмір цих даних виходячи з змісту змінної режиму CONTENT_LENGTH. Далі програма зчитує відповідну кількість байт даних з стандартного трафіка введення, записує їх до файлу. Після цього, після додавання заголовка "Stored information:", програма CGI записує отриманий рядок в стандартний вихідний трафік, передаючи її таким чином аплету Form. Оскільки при реальній роботі в мережі Internet вашу програму CGI можуть одночасно виконати декілька користувачів, для синхронізації коректування файлу бази даних ми застосували критичну ділянку. Внаслідок цього з файлом може працювати в будь-який момент часу тільки одна копія програми CGI. Ще одне зауваження стосується маршруту до файлу, що в нашому випадку створюється в кореневому каталозі диска C:. При налагоджені програми CGI на сервер вам необхідно забезпечити доступ на запис до директорії, в якій розміщується файл, для віддалених користувачів. Про те, як це зробити, ви можете дізнатися з документації на ваш сервер Web.
1.13. РАСТРОВІ ЗОБРАЖЕННЯ ТА МУЛЬТИПЛІКАЦІЯ
Одне з найбільш розповсюджених застосуваннь аплетів пов'язане з малюванням простих або анімованих растрових зображень. На серверах Web зображення звичайно запам'ятовуються в форматах GIF або JPEG. Обидва ці формату забезпечують ущільнення зображення, що надто актуально з-за невеликої швидкості передавання даних в мережі Internet. Малювання растрових зображень у прикладній програмі для операційної системи Windows, складених на мові програмування C - непроста задача. В класичному програмному інтерфейсі цієї операційної системи будуть відстуні функції, за допомогою яких можна було б безпосередньо малювати зміст файлів з растровими зображеннями. Програміст примушений працювати з заголовками таких файлів, виділяти таблицю кольорів та бітів зображень, створювати та здійснювати палітру, займатися виправленням ущільнення даних та таке інше. Проектувальник прикладної програми Java постійно перебує в набагато кращому положенні, оскільки бібліотеки класів Java містять прості у використанні та потужні засоби, призначені для роботи з растровими зображеннями. В цьому розділі ми навчимо вас малювати у вікні аплету зміст файлів GIF та JPEG, виконуючи у разі необхідності масштабування, а також створювати на базі таких файлів анімаційні зображення, відображаючи по черзі окремі кадри невеликого відеофільму. 1.13.1. Завантаження та малювання растрового зображенняЗавантаження растрового зображення з файлу виконується дуже просто - за допомогою методу 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 специфікує колір фону, на якому буде намальоване зображення. Як ви, певно, знаєте, зображення 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 виконує процедуру завантаження та малювання зображення, а після цього, не чекаючи його завершення, вертає управління. Оскільки завантаження файлу зображення по мережі може відняти немало часу, вона виконується несинхронізовано в окремій задачі. 1.13.2. Клас 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 (); Він звільняє ресурси, зайняті зображенням. 1.13. 3. Чекання завантаження зображеньЯк ми вже говорили в наших попередніх розділах, завантаження зображень з мережі Internet - тривала процедура, що в середньому йде зі швидкістю 1 Кбайт в секунду. Тому зображення вводяться в дію вікном перегляду в окремій задачі. При цьому метод getImage тільки створює об'єкт класу Image, а метод drawImage викличе завантаження зображення та малює його. Причому якщо файл зображення має більшу довжину, він буде з'являтися у вікні аплету поступово за мірою завантаженння. Однак в деяких випадках було би зручно спочатку ввести в дію зображення повністю, а лише після цього виконувати малювання, щоб зображення з'явилося на відображувальному екрані відразу. Крім того, аплет може малювати відразу декілька зображень в різних місцях свого вікна або відображати їх по черзі в одному й тому же місці для досягнення ефекту мультиплікації. Чи є спосіб визначити, коли зображення буде введене в дію повністю? Є, й причому цілих два. Один з них пов'язаний з використанням класу MediaTracker, спеціально призначеного для цієї мети та, достатньо зручного в використанні, іншого заснованого на перевизначенні одного з методів інтерфейсу ImageObserver. 1.13.3.1. Застосування класу MediaTrackerДля того щоб виконати чекання завантаження декількох зображень, простіше скористуватися класом MediaTracker, а не інтерфейсом ImageObserver. Як це зробити? Звичайно метод init аплету створює об'єкт класу MediaTracker за допомогою розробника та додає до нього всі зображення, завантаження яких необхідно дочекатися. 1.13.3.2. Генерування об'єкту класу MediaTrackerОб'єкт класу MediaTracker створюється наступним чином: MediaTracker mt; mt=new MediaTracker (this); Розробнику класу MediaTracker передається звертання на компоненту, для якої необхідно слідкувати завантаження зображень. В даному випадку це наш аплет, тому ми передаємо розробнику значенню this. 1.13.3.2. Додавання зображень в об'єкт класу 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 передається звертання на зображення, завантаження якого необхідно слідкувати, а у вигляді другого - специфікатор формату, який можна буде використовувати в процедурі стеження. Якщо все, що вам потрібне, це дочекатися завершення завантаження зображень, то для другого параметра ви можете вказати нульове значення. 1.13.3.3. Чекання завантаження доданих зображеньДля того щоб переконатися, що всі зображення введені в дію, ви можете скористуватися методом waitForAll. Цей метод викличе завантаження зображень, а також затримає виконання виклика трафіка до моменту повного завантаження всіх зображень, доданих в об'єкт класу MediaTracker: try { mt.waitForAll (); } catch (InterruptedException ex) { } Зверніть увагу, що метод waitForAll може створювати вилучення InterruptedException. Це вилучення виникає, якщо по будь-якій причині процедура чекання переривається. Найчастіше малювання виконується в окремому трафікові, тому метод waitForAll повинен викликатися на початку відповідного методу run. Нижче ми навели текст-джерела прикладної програми ImageDrawWait, в якій таке чекання виконується у методі paint, що призводить, однак, до блокування завдання аплету до моменту завантаження всіх зображень. В даному випадку це не критично, оскільки окрім малювання зображень наш аплет нічого не робить, однак більш прийнятним буде виконання тривалих процедур в окремому трафікові. 1.13.3.4. Інші методи класу 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, пов'язані з обробленням помилок: public boolean isErrorAny (); public boolean isErrorID (int id); public Object [] getErrorsAny (); public Object [] getErrorsID (int id); Методи isErrorAny та isErrorID дозволяють перевірити, чи виникла помилка при завантаженні, відповідно, будь-якого з зображень , що відслідковуються, або зображень з специфікованої групи. Якщо помилка пішла, повертається значення true, якщо ні - значення false. Методи getErrorsAny та getErrorsID повертають масив об'єктів, при чеканні завантаження яких пішла помилка. Перший з цих методів повертає масив для всіх об'єктів , що відслідковуються, другий - тільки об'єктів з специфікованої групи. 1.13.3.5. Застосування інтерфейсу ImageObserverДругий спосіб чекання завершення процедури завантаження зображень пов'язаний з інтерфейсом 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; Нижче ми навели стислий опис перерахованих ознак.
Аналізуючи стан ознак, метод imageUpdate може стежити за ходом завантаження зображень, відображаючи, наприклад, відсоток завершення процедури завантаження або виконуючи якісь інші команди. Якщо вам потрібне тільки дочекатися завершення процедури завантаження, достатньо використовувати ознаку ALLBITS. Для перевірки помилок скористуйтесь ознаками ERROR та ABORT. 1.13.4. Відео у вікні аплетуНайбільш динамічні сторінки сервера Web містять анімаційні зображення у вигляді невеликих відеофільмів. Ви можете підготувати відеофільм як файл AVI або як багатосекційний файл GIF. Файл AVI являє собою багатопроцесний файл, що містить відео і звук. Файли AVI можна створювати за допомогою спеціального відеоадаптеру, що здатний дискретизувати сигнал з відеокамери або відеомагнітофону, а також з окремих зображень, що складають кадри відеофільму. Помітимо, однак, що озвучений відеофільм в форматі AVI тривалістю в 1 хвилину займає мегабайти дискового простору. При існуючих на сьогодняшній день швидкостях передавання даних через Internet не має жодного сенсу призначати ресурси на сторінках сервера Web файли такого розміру. Багатосекційні файли GIF не містять звукових даних та складаються звичайно з одного-двох десятків кадрів. Для кожного такого кадру ви можете специфікувати час відображення і координати, де цей кадр буде відображатися. Можна також досягти зацикленого відображення відеофільму, створеного як багатосекційний файл GIF. Аплети Java надають вам ще одну можливість відображення невеликих відеофільмів на сторінках сервера Web. Для реалізації цієї можливості ви повинні підготувати й зарезервувати ресурси в одній з директорій сервера Web файли окремих кадрів відеофільму в форматі GIF або JPEG. Аплет Java повинен ввести в дію ці зображення, дочекавши завершення процедури завантаження, яке можна зробити або за допомогою розглянутого в цьому розділі класу MediaTracker або за допомогою інтерфейсу ImageObserver. Як тільки всі зображення будуть повністю введені в дію, аплет може починати їх по черзі відображення в циклі. Цей цикл повинен виконуватися в окремій задачі. Оскільки аплет повністю перевіряє відображення кадрів фільму, він може здійснювати ефекти, недосяжні при використанні файлів AVI або багатосекційних файлів GIF. Наприклад, аплет може накладати або змішувати кадри різних фільмів, малювати поверх кадрів довільні зображення або робити написи, масштабувати окремі фрагменти кадрів або весь кадр та таке інше. Тут все обмежується головним чином вашою фантазією. Оскільки ми вже навчилася виконувати всіх необхідні для показу відеофільму операції, перейти відразу до текст-джерел прикладна програма CDRotation. 1.13.5. Аплет CDRotationВ цьому розділі ми розкажемо про аплет CDRotation, в вікні якого обертається компакт-диск. В лівому верхньому куту кожного кадру відображається його порядковий номер (мал. 51). Цей номер не намальований в файлах кадрів, а надписується прикладною програмою після малювання чергового кадру. Таке неможливо, якщо розміщувати в документі HTML файл AVI або багатосекційний файл GIF.
Мал. 5.1. Зображення компакт-диску , що обертається, у вікні аплету CDRotation 1.13.5.1. Текст-джерела прикладної програмиГоловний файл текст-джерел прикладної програми CDRotation представлений в роздруку нижче. /*Файл CDRotation.java*\ 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; public String getAppletInfo () { return "Name: CDRotation"; } 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); } 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 ("Please, wait...", 10, dimAppWndDimension.height/2); } public void start () { if (m_CDRotation == null) { m_CDRotation=new Thread (this); m_CDRotation.start (); } } public void stop () { if (m_CDRotation!=null) { m_CDRotation. stop (); m_CDRotation=null; } } public void run () { m_nCurrImage=0; if (! m_fAllLoaded) { repaint (); m_Graphics=getGraphics (); m_Images=new Image [NUM_IMAGES]; MediaTracker tracker= new MediaTracker (this); String strImage; 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); } try { tracker.waitForAll (); m_fAllLoaded=! tracker.isErrorAny (); } catch (InterruptedException e) { } if (! m_fAllLoaded) { stop (); m_Graphics.drawString ( "Load error", 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; Thread.sleep (30); } catch (InterruptedException e) { stop (); } } } } Роздрук нижче містить текст-джерело документа HTML, створеного для аплету CDRotation. /*Файл CDRotation.tmp.html*\ <applet name="CDRotation" code="CDRotation" codebase= "file:/e:/sun/articles/vol13/src/CDRotation" width="500" height="600" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> </applet> 1.13.5.2. Опис текст-джерелРозглянемо найбільш важливі методи нашого аплету. 1.13.5.2.1. Метод start В завдання методу start, що одержує управління при відображенні вікна аплету, входить генерування та запуск трафіка, що відображає кадри відеофільму з зображенням компакт-диску , що обертається: if (m_CDRotation == null) { m_CDRotation=new Thread (this); m_CDRotation.start (); } Трафік створюється як об'єкт класу Thread, причому розробнику передається звертання на головний клас аплету. Тому при запускі трафіка управління отримає метод run, визначений в класі аплету. 1.13.5.2.2. Метод stop Метод stop зупиняє роботу трафіка, коли вікно аплету зникає з відображувального екрану: if (m_CDRotation!=null) { m_CDRotation.stop (); m_CDRotation=null; } Для вимкнення викликається метод stop. 1.13.5.2.3. Метод paint Відразу після одержання контролю, метод paint замальовує вікно аплету білим кольором та малює навколо нього чорну рамку. Після цього метод перевіряє зміст ознаки m_fAllLoaded. Ця ознака установлена в значення true, коли всі кадри відеофільму введені в дію та в значення false, коли завантаження кадрів ще не завершене. Останній режим виникає завжди при першому виклику методу paint. Якщо всі зображення введені в дію, метод paint викликає метод displayImage, визначений в нашій прикладній програмі: if (m_fAllLoaded) { displayImage (g); } Цей метод, про яке ми ще розкажемо докладніше, відображає у вікні аплету поточний кадр відеофільму. Якщо ж кадри відеофільму ще не введені в дію, в вікні аплету відображається відповідне повідомлення: else g. drawString ("Please, wait...", 10, dimAppWndDimension.height/2); 1.13.5.2.4. Метод 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 ( "Load error", 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 мілісекунд. 1.13.5.2.5. Метод 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);
1.14. ЗВУК В АПЛЕТАХ JAVA
Не можна сказати, що звукові можливості аплетів Java надмірно великі. Скоріше навпаки, вони мінімальні. Тим не менш, аплети можуть програвати звукові кліпи, записані в файлах формату AU, що прийшов зі світу комп'ютерів фірми Sun. Сказане, однак, не означає, що якщо у вас немає робочої станції Sun, то ви не зможите озвучити свої аплети. По-перше, в мережі Internet можна знайти багато готових звукових файлів AU, а по-друге, там же є програми для перетворення форматів звукових файлів. Одну з таких умовно-безкоштовних програм, що називається GoldWave, ви можете ввести в дію з сервера ftp.winsite.com. 1.14.1. Завантаження та програвання звукових файлівРобота зі звуковими файлами в численних випадках нагадує роботу з растровими графічними файлами. Спочатку ви повинні отримати звертання на інтерфейс 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, як неважко здогадатися з його назви, зупиняє програвання звукового файлу, як одноразове, так й виконоване у циклі. 1.14.2. Аплет PlayClipАплет PlayClip демонструє використання інтерфейсу AudioClip. В його вікні (мал. 52) існують три кнопки з назвами Play, Loop та Stop.
Мал. 52. Вікно аплету PlayClip Відразу після запуску аплету кнопка Stop постійно перебує в заблокованому стані. Якщо натиснути кнопку Play або Loop, запустить, відповідно, одноразове програвання або програвання в циклі файлу з ім'ям kaas.au, розташованого в тій же директорії, що і файл двійкових кодів аплету PlayClip. Коли починається програвання звукового файлу, кнопка Stop розблокується, що дозволяє зупинити програвання. 1.14.2.1. Текст-джерела прикладної програмиОсновний файл текст-джерел прикладної програми наведений в роздруку нижче. /*Файл PlayClip. java*\ import java.applet.; import java.awt.; public class PlayClip 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; public String getAppletInfo () { return "Name: PlayClip"; } public String [] [] getParameterInfo () { String [] [] info= { { PARAM_ClipName, "String", "Audioclip filename" }, }; return info; } 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); auClip=this. getAudioClip (getCodeBase (), m_ClipName); } 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; } 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); } public void start () { if (fLoopPlay) auClip.loop (); } public void stop () { if (fLoopPlay) auClip. stop (); } } В роздруку нижче ви знайдете текст-джерело документа HTML, створеного автоматично для нашої прикладної програми системою Java WorkShop. /*Файл PlayClip.tmp.html*\ <applet name="PlayClip" code="PlayClip" codebase= "file:/e:/sun/articles/vol14/src/PlayClip" width="200" height="100" align="Top" alt="If you had a java-enabled browser, you would see an applet here. "> <param name="ClipName" value='kaas.au'> <hr> If your browser recognized the applet tag, you would see an applet here. <hr> </applet> 1.14.2.2. Опис текст-джерелаВ головному класі аплету визначене декілька полів та методів. Розглянемо ці поля та найбільш важливі методи. 1.14.2.2.1. Поля класу PlayClip У полі m_ClipName запам'ятовується ім'я звукового файлу, що передається через параметр ClipName з документа HTML. За замовчанням для цього параметра використовується значення kaas.au. Рядок PARAM_ClipName запам'ятовує ім'я зазначеного вище параметра. Звертання на інтерфейс AudioClip запам'ятовується в поле auClip: AudioClip auClip; Наступні три поля запам'ятовують посилання на кнопки, призначені для контролю програванням звукового файлу: Button btPlay; Button btLoop; Button btStop; Поле fLoopPlay типу boolean використовується для ознаки, яким мітиться режим програвання звукового файлу в циклі. 1.14.2.2.2. Метод getParameterInfo Метод getParameterInfo вертає опис єдиного параметра нашого аплету, через що передається ім'я звукового файлу. 1.14.2.2.3. Метод 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); Останнє, що робить метод init перед тим як повернути контроль, це одержання звертання на інтерфейс AudioClip: auClip=this.getAudioClip ( getCodeBase (), m_ClipName); Адреса URL директорії, в якій розміщений аплет, визначиться за допомогою методу getCodeBase, про який ми говорили в попередньому розділі. 1.14.2.2.4. Метод 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 блокується. 1.14.2.2.5. Метод start Метод start одержує контроль при першому запуску аплету, а також коли сторінка документа з'являється знов після того як користувач тимчасово переходив до відображення інформації іншої сторінки. Наша реалізація методу start продовжує циклічне програвання, якщо воно виконувалося, коли користувач покинув сторінку з аплетом: if (fLoopPlay) auClip.loop (); 1.14.2.2.6. Метод stop Якщо користувач виконав програвання звукового файлу в циклі, а після цього перейшов до відображення інформації іншої сторінки, метод stop зупиняє циклічне програвання: if (fLoopPlay) auClip.stop (); Коли користувач повернеться до відображення інформації нашої сторінки, метод start, описаний вище, продовжить програвання звукового файлу. КомментарииКомментариев пока нет Пожалуйста, авторизуйтесь, чтобы оставить комментарий. |