6. Настройка системы LayoutManager

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

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

В чем трудность создания пользовательского интерфейса для мультиплатформных систем?

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

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

Как мы уже говорили в начале предыдущей главы, расположением компонент внутри окна контейнера (например, внутри окна аплета) управляет система Layout Manager. Способ, которым она это делает, весьма непривычен для тех, кто создавал приложения Windows. Выбор этого способа обоснован необходимостью обеспечения совместимости с различными компьютерными платформами.

Режимы системы Layout Manager

Прежде чем мы рассмотрим различные режимы компоновки системы Layout Manager, вспомним, как происходит наследование класса Applet (рис. 6.1).

Рис. 6.1. Наследование класса Applet

Класс Applet наследуется от класса Panel, который, в свою очередь, наследуется от класса Container и Component. Класс Container пользуется интерфейсом LayoutManager, что позволяет выбирать для контейнеров один из нескольких режимов размещения компонент в окне контейнера.

Что же касается класса Panel, то для него по умолчанию выбирается режим размещения компонент с названием Flow Layout. Разумеется, вы можете выбрать другой режим размещения, указав его явным образом.

Ниже мы перечислили все возможные режимы системы Layout Manager:

Режим размещения компонент Описание
FlowLayout Компоненты заполняют окно контейнера “потоком” по мере их добавления методом add. Они размещаются слева направо и сверху вниз
GridLayout Компоненты размещаются в виде таблицы по мере добавления слева направо и сверху вниз. Для этой таблицы можно указать количество столбцов и строк
GridBagLayout Аналогично предыдущему, однако при добавлении компонент в таблицу можно указать координаты ячейки, в которую помещается компонента
BorderLayout При размещении компоненты указывается одно из нескольких направлений: юг, север, запад, восток, центр. Направление определяется относительно центра окна контейнера
CardLayout Размещение компонент друг над другом в одном окне. Этот режим позволяет организовать набор диалоговых панелей в виде блокнота

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

Далее на примере конкретных приложений мы рассмотрим использование перечисленных выше режимов системы Layout Manager.

Режим FlowLayout

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

Ниже мы привели краткое описание класса FlowLayout:

public class java.awt.FlowLayout
  extends java.lang.Object
  implements java.awt.LayoutManager
{
  // -----------------------------------------------------
  // Поля
  // -----------------------------------------------------
  
  // Способы выравнивания
  public final static int CENTER; // центрирование
  public final static int LEFT;   // по левой границе
  public final static int RIGHT;  // по правой границе

  // -----------------------------------------------------
  // Конструкторы
  // -----------------------------------------------------

  // Без указания выравнивания и зазора между компонентами
  public FlowLayout();

  // С указанием выравнивания
  public FlowLayout(int align);

  // С указанием выравнивания и зазора между компонентами
  // по вертикали и горизонтали
  public FlowLayout(int align, int hgap, int vgap);

  // -----------------------------------------------------
  // Методы
  // -----------------------------------------------------
  
  // Не используется
  public void addLayoutComponent(String name,
    Component comp);

  // Предназначен для того чтобы компоненты могли
  // установить для себя предпочтительный размер
  public void layoutContainer(Container target);

  // Определение минимального размера окна контейнера,
  // необходимого для размещения всех компонент
  public Dimension minimumLayoutSize(Container target);

  // Определение предпочтительного размера окна контейнера,
  // необходимого для размещения всех компонент
  public Dimension preferredLayoutSize(Container target);

  // Удаление компоненты из контейнера
  public void removeLayoutComponent(Component comp);

  // Получение строки названия метода компоновки
  public String toString();
}

Обычно приложения не вызывают методы класса FlowLayout, устанавливая варианты компоновки при помощи конструкторов.

Первый конструктор класса FlowLayout, не имеющий параметров, устанавливает по умолчанию режим центрирования компонент и зазор между компонентами по вертикали и горизонтали, равный 5 пикселам. Именно этот режим и использовался во всех наших аплетах из предыдущей главы, так как именно он применяется по умолчанию объектами класса Panel, от которого наследуется класс Applet.

С помощью второго конструктора вы можете выбрать режим размещения с заданным выравниванием компонент в окне контейнера по горизонтали. В качестве параметров этому конструктору необходимо передавать значения FlowLayout.LEFT, FlowLayout.RIGHT, или FlowLayout.CENTER. Зазор между компонентами будет при этом равен по умолчанию 5 пикселам.

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

Режим GridLayout

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

public class java.awt.GridLayout
  extends java.lang.Object 
  implements java.awt.LayoutManager
{
  // -----------------------------------------------------
  // Конструкторы
  // -----------------------------------------------------

  // Создание таблицы с заданным 
  // количеством строк и столбцов
  public GridLayout(int rows, int cols);

  // Создание таблицы с заданным  количеством строк и 
  // столбцов и с заданным зазором между компонентами
  public GridLayout(int rows, int cols, int hgap, int vgap);

  // -----------------------------------------------------
  // Методы
  // -----------------------------------------------------
  public void addLayoutComponent(String name,
    Component comp);
  public void layoutContainer(Container target);
  public Dimension minimumLayoutSize(Container target);
  public Dimension preferredLayoutSize(Container target);
  public void removeLayoutComponent(Component comp);
  public String toString();
}

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

Заметим, что оба параметра rows и cols не могут быть равны нулю одновременно.

Приложение Grid

Приложение Grid демонстрирует использование режима размещения компонент GridLayout. В окне аплета мы создали таблицу размером 3х3, разместив в ее ячейках восемь кнопок (рис. 6.2).

Рис. 6.2. Окно приложения Grid

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

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

Исходный текст аплета Grid приведен в листинге 6.1.

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

// =========================================================
// Режим компоновки GridLayout
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =========================================================
import java.applet.*;
import java.awt.*;

public class Grid extends Applet
{
  // Создаем ссылки на объекты типа Button
  Button btn1;
  Button btn2;
  Button btn3;
  Button btn4;
  Button btn5;
  Button btn6;
  Button btn7;
  Button btn8;

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

  // -------------------------------------------------------
  // init
  // Метод, получающий управление при инициализации аплета
  // -------------------------------------------------------
  public void init()
  {
    // Устанавливаем желтый цвет фона
    setBackground(Color.yellow);

    // Создаем кнопки
    btn1 = new Button("Button 1");
    btn2 = new Button("Button 2");
    btn3 = new Button("Button 3");
    btn4 = new Button("Button 4");
    btn5 = new Button("Button 5");
    btn6 = new Button("Button 6");
    btn7 = new Button("Button 7");
    btn8 = new Button("Button 8");

    // Устанавливаем режим GridLayout    
    setLayout(new GridLayout(3, 3));
    
    // Добавляем кнопки в контейнер
    add(btn1);
    add(btn2);
    add(btn3);
    add(btn4);
    add(btn5);
    add(btn6);
    add(btn7);
    add(btn8);
  }

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

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

      // Получаем название кнопки
      sTextLabel = btn.getLabel();

      // Записываем название кнопки 
      // в строку состояния навигатора
      showStatus("Button (\"" + sTextLabel + "\") pressed");

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

    // Если событие вызвано не кнопкой, 
    // мы его не обрабатываем
    return false;
  }
  
  // -------------------------------------------------------
  // paint
  // Метод paint, выполняющий рисование в окне аплета
  // -------------------------------------------------------
  public void paint(Graphics g)
  {
    // Определяем текущие размеры окна аплета
    Dimension dimAppWndDimension = size();
    
    // Выбираем в контекст отображения черный цвет
    g.setColor(Color.black);

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

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

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

<html>
<head>
<title>Grid</title>
</head>
<body>
<hr>
<applet
    code=Grid.class
    id=Grid
    width=320
    height=240 >
</applet>
<hr>
<a href="Grid.java">The source.</a>
</body>
</html>

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

В исходном тексте приложения Grid вы сможете разобраться саомстоятельно. Заметим только, что в методе init мы вызываем метод setLayout, передавая ему объект класса GridLayout:

setLayout(new GridLayout(3, 3));

В свою очередь, параметры конструктора объекта GridLayout определяют, что для размещения компонент будет использована таблица, состоящая из трех строк и трех столбцов.

Компоненты (кнопки) добавляются в окно аплета хорошо известным вам способом - с помощью метода add:

add(btn1);
add(btn2);
add(btn3);
add(btn4);
add(btn5);
add(btn6);
add(btn7);
add(btn8);

Как видно из рис. 6.2, таблица заполняется кнопками слева направо и сверху вниз, как это и предполагает режим компоновки GridLayout.

Режим BorderLayout

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

Ниже приведено краткое описание класса BorderLayout:

public class java.awt.BorderLayout
  extends java.lang.Object
  implements java.awt.LayoutManager
{
  // -----------------------------------------------------
  // Конструктор
  // -----------------------------------------------------
  public BorderLayout();	
  public BorderLayout(int hgap, int vgap); 

  // -----------------------------------------------------
  // Методы
  // -----------------------------------------------------
  public void  addLayoutComponent(String name, 
    Component comp);
  public void layoutContainer(Container target); 
  public Dimension minimumLayoutSize(Container target); 
  public Dimension preferredLayoutSize(Container target); 
  public void removeLayoutComponent(Component comp); 
  public String toString();
}

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

Добавляя компоненты к контейнеру, вы должны использовать метод add с двумя параметрами, первый из которых указывает направление размещения, а второй - ссылку на добавляемый объект:

add("North",  btn1);
add("East",   btn2);
add("West",   btn3);
add("South",  btn4);
add("Center", btn5);

Приложение Border

В приложении Border создается пять кнопок, которые размещаются в режиме BorderLayout (рис. 6.3).

Рис. 6.3. Окно аплета Border

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

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

Для центральной кнопки было выделено все оставшееся пространство в центре окна аплета.

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

Исходный текст приложения Border приведен в листинге 6.3.

Листинг 6.3. Файл Border\Border.java

// =========================================================
// Режим компоновки BorderLayout
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =========================================================
import java.applet.*;
import java.awt.*;

public class Border extends Applet
{
  // Создаем ссылки на объекты типа Button
  Button btn1;
  Button btn2;
  Button btn3;
  Button btn4;
  Button btn5;

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

  // -------------------------------------------------------
  // init
  // Метод, получающий управление при инициализации аплета
  // -------------------------------------------------------
  public void init()
  {
    // Устанавливаем желтый цвет фона
    setBackground(Color.yellow);

    // Создаем кнопки
    btn1 = new Button("Button North");
    btn2 = new Button("Button East");
    btn3 = new Button("Button West");
    btn4 = new Button("Button South");
    btn5 = new Button("Button Center");

    // Устанавливаем режим GridLayout    
    setLayout(new BorderLayout());
    
    // Добавляем кнопки в контейнер
    add("North",  btn1);
    add("East",   btn2);
    add("West",   btn3);
    add("South",  btn4);
    add("Center", btn5);
  }

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

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

      // Получаем название кнопки
      sTextLabel = btn.getLabel();

      // Записываем название кнопки 
      // в строку состояния навигатора
      showStatus("Button (\"" + sTextLabel + "\") pressed");

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

    // Если событие вызвано не кнопкой, 
    // мы его не обрабатываем
    return false;
  }
  
  // -------------------------------------------------------
  // paint
  // Метод paint, выполняющий рисование в окне аплета
  // -------------------------------------------------------
  public void paint(Graphics g)
  {
    // Определяем текущие размеры окна аплета
    Dimension dimAppWndDimension = size();
    
    // Выбираем в контекст отображения черный цвет
    g.setColor(Color.black);

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

Документ HTML, который был создан для нашего аплета, вы найдете в листинге 6.4.

Листинг 6.4. Файл Border\Border.html

<html>
<head>
<title>Border</title>
</head>
<body>
<hr>
<applet
    code=Border.class
    id=Border
    width=320
    height=240 >
</applet>
<hr>
<a href="Border.java">The source.</a>
</body>
</html>

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

Все самое интересное в приложении Border происходит в методе init, поэтому мы ограничимся описанием только этого метода.

Прежде всего метод init устанавливает желтый цвет фона:

setBackground(Color.yellow);

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

Далее метод init создает пять кнопок для размещения в окне аплета. Здесь тоже для вас нет ничего нового:

btn1 = new Button("Button North");
btn2 = new Button("Button East");
btn3 = new Button("Button West");
btn4 = new Button("Button South");
btn5 = new Button("Button Center");

Далее мы устанавливаем режим размещения компонент в окне контейнера, вызывая для этого метод setLayout:

setLayout(new BorderLayout());

В качестве параметра методу setLayout передается ссылка на только что созданный объект класса BorderLayout. Так как выбран конструктор класса BorderLayout без параметров, зазор между компонентами будет отсутствовать.

Добавление компонент выполняется методом add с указанием направления расположения компоненты:

add("North",  btn1);
add("East",   btn2);
add("West",   btn3);
add("South",  btn4);
add("Center", btn5);

Заметим, что нельзя размещать несколько компонент в одном и том же направлении.

Режим CardLayout

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

Класс CardLayout содержит два конструктора и несколько методов:

public class java.awt.CardLayout
  extends java.lang.Object
  implements java.awt.LayoutManager
{
  // -----------------------------------------------------
  // Конструкторы
  // -----------------------------------------------------

  // Режим без зазоров
  public CardLayout();

  // Режим с зазорами по вертикали и горизонтали
  // между компонентами и окном контейнера
  public CardLayout(int hgap, int vgap);

  // -----------------------------------------------------
  // Методы
  // -----------------------------------------------------

  // Добавление компоненты с указанием имени
  public void addLayoutComponent(String name, 
    Component comp);

  // Отображение первой страницы блокнота
  public void first(Container target);

  // Отображение последней страницы блокнота
  public void last(Container target);

  // Отображение следующей страницы блокнота
  public void next(Container target);

  // Отображение предыдущей страницы блокнота
  public void previous(Container target);

  // Выполнение размещения компонент
  public void layoutContainer(Container target);

  // Определение минимальных размеров окна,
  // необходимых для размещения компонент
  public Dimension minimumLayoutSize(Container target);

  // Определение предпочтительных размеров окна,
  // необходимых для размещения компонент
  public Dimension preferredLayoutSize(Container target);

  // Удаление заданной компоненты
  public void removeLayoutComponent(Component comp);

  // Отображение произвольной страницы блокнота
  // по ее имени
  public void show(Container target, String name);

  // Получение текстовой строки названия режима размещения
  public String toString();
}

Как пользоваться режимом размещения CardLayout?

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

Такие методы, как first, last, next и previous позволяют отображать, соответственно, первую, последнюю, следующую и предыдущую страницу блокнота. Если вызвать метод next при отображении последней страницы, в окне появится первая страница. Аналогично, при вызове метода previous для первой страницы блокнота вы увидите последнюю страницу.

А как отобразить произвольную страницу, не перебирая их по одной методами next и previous?

Для этого существует метод show. Учтите, что этот метод позволяет отображать только такие страницы, при добавлении которых методом add было указано имя, например:

pCardPanel.add("BackgroundColor", pBackgroundColor);
pCardPanel.add("ForegroundColor", pForegroundColor);
pCardPanel.add("Font", pFont);

Здесь в панель pCardPanel добавляются панели pBackgroundColor, pForegroundColor и pFont, имеющие имена, соответственно, "BackgroundColor", "ForegroundColor" и "Font".

Режим GridBagLayout

Режим GridBagLayout намного сложнее только что описанного режима GridLayout. Он позволяет размещать компоненты разного размера в таблице, задавая при этом для отдельных компонент размеры отступов и количество занимаемых ячеек.

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

Если вы создаете аплеты для размещения в документах HTML, никто не заставляет вас ограничиваться только одним аплетом для одного документа HTML - вы можете разместить там произвольное количество аплетов, организовав взаимодействие с одной стороны, между отдельными аплетами, а с другой - между аплетами и расширениями сервера WWW.

Тех, кого интересует режим GridBagLayout, мы адресуем к документации, которая входит в комплект Microsoft Visual J++, а также к книге Д. Родли “Создание Java-апплетов”, которая издана на русском языке.

В интегрированной системе разработки приложений Java Microsoft Visual J++ версии 1.1 имеется система автоматизированного проектирования пользовательского интерфейса, в результате работы которой создаются исходные тексты классов. Размещение органов управления при этом выполняется интерактивными средствами, аналогичными средствам разработки диалоговых панелей для приложений Microsoft Windows. В следующем томе “Библиотеки системного программиста”, посвященном Java, мы научим вас пользоваться этой системой.