6. Взаимодействие между аплетами

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

Как аплеты могут взаимодействовать друг с другом?

С помощью интерфейса AppletContext, о котором вы скоро узнаете, аплеты могут получать списки всех аплетов для текущего документа HTML. Эти списки состоят из ссылок на соответствующие объекты класса Applet, пользуясь которыми можно получить доступ к полям и методам, определенным в этих аплетах как public, могут получать строку описания аплета, описание параметров и значения, передаваемые аплетам через параметры в документе HTML.

Таким образом, аплеты могут взаимодействовать друг с другом достаточно тесным образом.

Использование интерфейса AppletContext

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

Рассмотрим этот процесс подробно.

Получение контекста аплетов

Для получения контекста аплетов, или ссылки на интерфейс AppletContext вы должны воспользоваться методом getAppletContext, определенным в классе Applet:

AppletContext appContext;
appContext = getAppletContext();

Далее, вызывая методы, определенные в интерфейсе AppletContext, вы можете получить ссылку на конкретный аплет или список ссылок на все аплеты.

Получение ссылки на аплет

Метод getApplet возвращает ссылку на аплет, заданный своим именем:

public abstract Applet getApplet(String name);

Это имя должно быть указано в параметре NAME оператора <APPLET> языка HTML, или оно должно передаваться аплету через параметр с именем NAME.

Первый из этих способов демонстрируется ниже:

<applet
    code=MyApplet.class
    id=MyApplet
    width=320
    height=100 
    name="TestApplet">
</applet>

Здесь параметр NAME задает для аплета MyApplet имя TestApplet.

Второй способ был использован нами в следующем фрагменте документа HTML:

<applet
    code=MyApplet.class
    id=MyApplet
    width=320
    height=100 >
    <param name=TestApplet value="TestApplet">
</applet>

Если аплет с указанным именем имеется в текущем документе HTML, метод getApplet возвращает ссылку на соответствующий объект. Пользуясь этой ссылкой, можно получить доступ к полям и методам, определенным в аплете как public. Если же аплет не найден, метод getApplet возвращает значение null.

Получение списка всех аплетов

С помощью метода getApplet вы сможете получить ссылку на один аплет, заданный своим именем, но наша задача - получить ссылки на все аплеты, расположенные в текущем документе HTML. Это можно сделать с помощью метода getApplets, определенного следующим образом:

public abstract Enumeration getApplets();

Перед вызовом этого метода вы должны определить список класса Enumeration, например, следующим образом:

Enumeration eApplets;
eApplets = appContext.getApplets();

Просмотр списка аплетов

Список класса Enumeration можно просмотреть в цикле только один раз, вызывая для получения очередного элемента списка метод nextElement:

while(eApplets.hasMoreElements())
{
  Applet currentApplet = (Applet)(eApplets.nextElement());
  . . .
}

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

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

if(currentApplet == this)
{
  // Обработка "своего" аплета
}

Получение строки информации об аплете

Обратите внимание, что в каждом нашем аплете мы определяли метод getAppletInfo:

public String getAppletInfo()
{
  return "Name: Inspector\r\n" +
    "Author: Alexandr Frolov\r\n" +
    "E-mail: frolov@glas.apc.org" +
    "WWW:    http://www.glasnet.ru/~frolov" +
    "Created with Microsoft Visual J++ Version 1.0";
}

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

В самом деле, просматривая в цикле список аплетов, расположенных в текущем документе HTML, аплет-инспектор может вызывать для каждого найденного аплета метод getAppletInfo с целью получения строки, идентифицирующей данный аплет:

appName = currentApplet.getAppletInfo();

Если же аплет не обеспечил метод getAppletInfo, то будет вызван одноименный метод из базового класса Applet, который просто возвращает значение null. Очевидно, это значение нельзя использовать для идентификации аплета.

Получение информации о параметрах аплета

Если аплет получает из документа HTML параметры, желательно (но вовсе не обязательно) определить метод getParameterInfo. Этот метод возвращает массив строк описаний параметров, в котором находятся имена и типы параметров, а также строки описаний параметров.

Для примера мы привели фрагмент исходного текста приложения TEXTOUT, описанного нами в 30 томе, посвященном разработке приложений Java:

public String[][] getParameterInfo()
{
  String[][] info =
  {
    { PARAM_Str1, "String", "Text string to write" },
    { PARAM_Str2, "String", "Text string to write" },
    { PARAM_Str3, "String", "Text string to write" },
      . . .
    { PARAM_Font1, "String", "Text font" },
    { PARAM_Font2, "String", "Text font" },
    { PARAM_Font3, "String", "Text font" },
      . . .
    { PARAM_Type1, "String", "Font type" },
    { PARAM_Type2, "String", "Font type" },
    { PARAM_Type3, "String", "Font type" },
  };
  return info;    
}

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

Получение значений параметров аплета

Для получения значения заданного параметра найденного аплета вы можете воспользоваться методом getParameter:

String sParameter = currentApplet. GetParameter("name");

Здесь мы получаем значение параметра с именем NAME.

Обращение к полям и методам других аплетов

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

Поясним это.

Все аплеты, как вы знаете, происходят от класса Applet. Они добавляют в этот класс свои поля и методы, а также переопределяют методы из базового класса.

В процессе поиска аплетов метод nextElement возвращает ссылку на объект, принадлежащий к классу Object, который мы можем преобразовать к классу Applet:

Applet currentApplet = (Applet)(eApplets.nextElement());

Однако такое преобразование сможет открыть нам доступ только к тем полям и методам, которые определены в базовом классе. Для того чтобы получить доступ к полям и методам класса найденного аплета, мы должны преобразовать значение, полученное от метода nextElement, к ссылке на класс этого аплета.

Как это можно сделать?

Рассмотрим конкретный пример, использованный нами в приложении Inspector, полные исходные тексты которого вы найдете ниже.

Это приложение управляет работой аплета Audio, описанного в предыдущей главе и предназначенного для проигрывания звукового файла. В классе Audio определено поле auClip, в котором хранится ссылка на интерфейс AudioClip:

public class Audio extends Applet
{
  private String m_ClipName = "kaas.au";
  private final String PARAM_ClipName = "ClipName";
  AudioClip auClip;
  . . .
}

Аплет Inspector получает доступ к полю auClip и вызывает методы, предназначенные для управления проигрыванием звукового файла. Таким образом, аплет Inspector пользуется полем auClip, определенным в другом аплете.

Чтобы это стало возможным, в исходном тексте аплета Inspector импортируется класс Audio, как это показано ниже:

import java.applet.*;
import java.awt.*;
import java.util.*;
import Audio;

Когда в процессе поиска аплетов аплет Inspector обнаруживает аплет Audio, он сохраняет ссылку на этот аплет в поле appAudio, выполняя явное преобразование типов:

Audio appAudio = null;
  . . .
if(appName.equals("Name: Audio"))
{
  appAudio = (Audio)currentApplet;
}

Теперь, пользуясь значением из поля appAudio, можно обращаться к полю auClip, определенному в аплете Audio:

appAudio.auClip.play();

Приложение Inspector

Аплет Inspector располагается в одном документе HTML с приложениями Audio и Rectangles, которые уже были описаны в нашей книге (рис. 6.1).

Рис. 6.1. Документ HTML, в котором расположены три аплета - Inspector, Audio и Rectangles

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

В нижней части аплета Inspector отображается список имен аплетов, найденных в текущем документе HTML. Для списка используются первые строки описаний аплетов, полученные методом getAppletInfo.

Исходные тексты приложения

Основной файл исходных текстов вы найдете в листинге 6.1.

Листинг 6.1. Файл Inspector\Inspector.java

// =========================================================
// Аплет, который получает список всех аплетов
// и управляет аплетом Audio
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =========================================================
import java.applet.*;
import java.awt.*;
import java.util.*;

// Импортируем класс Audio, так как нам нужен доступ к 
// его полям
import Audio;

public class Inspector extends Applet
{
  // Контекст аплетов
  AppletContext appContext;

  // Список аплетов, расположенных в документе HTML
  Enumeration eApplets;

  // Ссылка на аплет Audio
  Audio appAudio = null;

  // Кнопка для однократного проигрывания
  Button btPlay;

  // Кнопка для проигрывания в цикле
  Button btLoop;

  // Кнопка для остановки проигрывания
  Button btStop;

  // -------------------------------------------------------
  // getAppletInfo
  // Метод, возвращающей строку информации об аплете
  // -------------------------------------------------------
  public String getAppletInfo()
  {
    return "Name: Inspector\r\n" +
      "Author: Alexandr Frolov\r\n" +
      "E-mail: frolov@glas.apc.org" +
      "WWW:    http://www.glasnet.ru/~frolov" +
      "Created with Microsoft Visual J++ Version 1.0";
  }

  // -------------------------------------------------------
  // init
  // Вызывается во время инициализации аплета
  // -------------------------------------------------------
  public void init()
  {
      // Создаем кнопку для однократного проигрывания
    btPlay = new Button("Play");

    // Создаем кнопку для проигрывания в цикле
    btLoop = new Button("Loop");

    // Создаем кнопку для остановки проигрывания
    btStop = new Button("Stop");

    // Блокируем эту кнопку, так как пока еще
    // проигрывание не запущено
    btStop.disable();

    // Добавляем кнопки в окно аплета
    add(btPlay);
    add(btLoop);
    add(btStop);
  }
  
  // -------------------------------------------------------
  // paint
  // Метод paint, выполняющий рисование в окне аплета
  // -------------------------------------------------------
  public void paint(Graphics g)
  {
    // Определяем текущие размеры окна аплета
    Dimension dimAppWndDimension = size();
    
    // Выбираем в контекст отображения желтый цвет
    g.setColor(Color.yellow);
    
    // Закрашиваем внутреннюю область окна аплета
    g.fillRect(0, 0, 
      dimAppWndDimension.width  - 1, 
      dimAppWndDimension.height - 1);

    // Выбираем в контекст отображения черный цвет
    g.setColor(Color.black);

    // Рисуем рамку вокруг окна аплета
    g.drawRect(0, 0, 
      dimAppWndDimension.width  - 1, 
      dimAppWndDimension.height - 1);

    // Получаем контекст аплетов
    appContext = getAppletContext();
    
    // Получаем список всех аплетов в документе HTML
    eApplets = appContext.getApplets();

    // Сбрасываем счетчик цикла
    int i = 0;
    
    // Цикл по аплетам
    while(eApplets.hasMoreElements())
    {
      // Имя текущего аплета
      String appName;
      
      // Получаем ссылку на очередной аплет
      Applet currentApplet = 
        (Applet)(eApplets.nextElement());

      // Получаем строку информации об аплете
      appName = currentApplet.getAppletInfo();

      // Обрезаем строку, удаляя символ конца строки
      StringTokenizer st;
      st   = new StringTokenizer(appName, "\r\n");
      appName = new String((String)st.nextElement());
      
      // Отображаем имя найденного аплета
      g.drawString(appName , 10, 15 * i + 50);

      // Если нашли аплет Audio, запоминаем ссылку
      // на него в поле appAudio
      if(appName.equals("Name: Audio"))
      {
        appAudio = (Audio)currentApplet;
      }

      // Увеличиваем счетчик аплетов
      i++;
    }
  }

  // -------------------------------------------------------
  // action
  // Метод вызывается, когда пользователь выполняет
  // действие над компонентами
  // -------------------------------------------------------
  public boolean action(Event evt, Object obj)
  {
    // Ссылка на кнопку, от которой пришло сообщение
    Button btn;

    // Проверяем, что событие вызвано кнопкой, а не
    // другим компонентом
    if(evt.target instanceof Button)
    {
      // Получам ссылку на кнопку, вызвавшую событие
      btn = (Button)evt.target;

      // Выполняем ветвление по кнопкам
      
      // Нажата кнопка однократного проигрывания
      if(evt.target.equals(btPlay))
      {
        // Запускаем однократное проигрывание
        appAudio.auClip.play();

        // Разблокируем кнопку остановки проигрывания
        btStop.enable();
      }

      // Нажата кнопка проигрывания в цикле
      else if(evt.target.equals(btLoop))
      {
        // Запускаем проигрывание в цикле
        appAudio.auClip.loop();

        // Разблокируем кнопку остановки проигрывания
        btStop.enable();
      }

      // Нажата кнопка остановки проигрывания
      else if(evt.target.equals(btStop))
      {
        // Останавливаем проигрывание
        appAudio.auClip.stop();

        // Блокируем кнопку остановки проигрывания
        btStop.disable();
      }

      // Если событие возникло от неизвестной кнопки,
      // мы его не обрабатываем
      else
      {
        return false;
      }

      // Возвращаем признак того, что мы обработали событие
      return true;
    }

    // Если событие вызвано не кнопкой, 
    // мы его не обрабатываем
    return false;
  }
}

Файл документа HTML, содержащий все три аплета, приведен в листинге 6.2.

Листинг 6.2. Файл Inspector\Inspector.html

<html>
<head>
<title>Inspector</title>
</head>
<body>
<hr>
<applet
    code=Inspector.class
    id=Inspector
    width=320
    height=100 >
</applet>
<hr>
<applet
    code=Audio.class
    id=Audio
    width=320
    height=30 >
    <param name=ClipName value="kaas.au">
</applet>
<hr>
<applet
    code=Rectangles.class
    id=Rectangles
    width=320
    height=240 >
</applet>
<hr>
<a href="Inspector.java">The source.</a>
</body>
</html>

Описание исходных текстов

Обращаем еще раз ваше внимание на то, что в исходном тексте аплета Inspector импортируется описание класса Audio:

import Audio;

Это необходимо для того чтобы аплет мог получить доступ к полю auClip, определенному в этом аплете.

Рассмотрим поля и самые важные методы класса Inspector.

Поля класса Inspector

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

Этот список будет записан в поле eApplets класса Enumeration.

Когда в процессе поиска наш аплет найдет аплет Audio, то в поле appAudio будет записана ссылка на него.

Кроме того, в классе Inspector определены поля btPlay, btLoop и btStop для хранения ссылок на кнопки управления аплетом Audio.

Метод getAppletInfo

Метод getAppletInfo предоставляет другим заинтересованным аплетам (и себе, в частности) строку информации об аплете Inspector. Эта строка аналогична строкам, которые возвращают другие наши аплеты.

Метод init

Метод init создает три кнопки, предназначенные для управления аплетом Audio, причем кнопка с названием Stop блокируется:

btPlay = new Button("Play");
btLoop = new Button("Loop");
btStop = new Button("Stop");
btStop.disable();

Далее созданные кнопки добавляются в окно аплета Inspector.

add(btPlay);
add(btLoop);
add(btStop);

После раскрашивания фона окна и рисования рамки метод paint получает с помощью метода getAppletContext ссылку на интерфейс AppletContext:

appContext = getAppletContext();

Далее с помощью этой ссылки и метода getApplets приложение получает список всех аплетов, расположенных в текущем документе HTML;

eApplets = appContext.getApplets();

Вслед за этим метод paint запускает цикл, в котором он получает ссылки на все найденные аплеты:

while(eApplets.hasMoreElements())
{
  . . .
}

В этом цикле с помощью метода nextElement приложение получает ссылку на очередной аплет и, после преобразования ее к типу Applet, сохраняет в переменной currentApplet:

Applet currentApplet = (Applet)(eApplets.nextElement());

Для каждого найденного аплета вызывается метод getAppletInfo:

appName = currentApplet.getAppletInfo();

Полученная строка обрезается до первого символа возврата каретки или перевода на новую строку и записывается в переменную appName:

StringTokenizer st;
st = new StringTokenizer(appName, "\r\n");
appName = new String((String)st.nextElement());

Содержимое этой переменной (имя аплета) отображается в окне аплета Inspector со сдвигом по вертикали, который завивит от номера найденного аплета:

g.drawString(appName , 10, 15 * i + 50);

В том случае, если в процессе получения строк информации об аплете был найден аплет Audio, выполняется преобразование типа ссылки на этот аплет и сохранение этой ссылки в поле appAudio:

if(appName.equals("Name: Audio"))
{
  appAudio = (Audio)currentApplet;
}

Метод action

Метод action обрабатывает события, вызванные нажатием кнопок в окне аплета Inspector. Обработка заключается в вызове сооветствующего метода с использованием ссылки на аплет Audio. Например, если пользователь нажал кнопку Play, метод action вызывает метод play:

if(evt.target.equals(btPlay))
{
  appAudio.auClip.play();
  btStop.enable();
}

Обратите внимание, что здесь мы ссылаемся через поле appAudio на поле auClip, определенное в аплете Audio.