Трудность при создании независимой от
платформы библиотеки заключается в том, что ее
разработчикам либо приходится требовать, чтобы
все приложения на всех платформах вели себя и
выглядели одинаково, либо для поддержки, скажем,
трех различных разновидностей интерфейса
приходится писать в три раза больше кода.
Существуют два
взгляда на эту проблему. Один подход заключается
в том, что упор делается на графику низкого
уровня — рисование пикселей, при этом
разработчики библиотеки сами заботятся о
внешнем виде каждого компонента. При другом
подходе создаются абстракции, подходящие для
библиотек каждой из операционных систем, и
именно “родные” пакеты данной операционной
системы служат подъемной силой для
архитектурно-нейтральной библиотеки на каждой
из платформ. В
Java при
создании библиотеки Abstraction Window Toolkit
(AWT) выбран второй подход.
Классы Graphics и Fonts мы уже обсуждали в
главе 15. В главе 18 будут обсуждаться различные возможности
работы с изображениями. В данной главе мы
пройдемся по базовой архитектуре AWT, касающейся
интерфейсных объектов.
Компоненты
Component — это абстрактный класс, который
инкапсулирует все атрибуты визуального
интерфейса - обработка ввода с клавиатуры,
управление фокусом, взаимодействие с мышью,
уведомление о входе/выходе из окна, изменения
размеров и положения окон, прорисовка своего
собственного графического представления
, сохранение
текущего текстового шрифта, цветов фона и переднего плана
(более 10 методов). Перейдем к некоторым конкретным подклассам
класса Component.
Container
Container — это абстрактный подкласс
класса Component, определяющий дополнительные
методы, которые дают возможность помещать в него
другие компоненты, что дает возможность
построения иерархической системы визуальных
объектов. Container отвечает за расположение
содержащихся в нем
компонентов с
помощью интерфейса LayoutManager, описание которого
будет позднее в этой главе.
Panel
Класс Panel — это очень простая
специализация класса Container. В отличие от
последнего, он не является абстрактным классом.
Поэтому о Panel можно думать, как о допускающем
рекурсивную вложенность экранном компоненте.
С помощью метода add
в объекты Panel можно добавлять другие компоненты.
После того, как в него добавлены какие-либо
компоненты, можно вручную задавать их положение
и изменять размер с помощью методов move, resize и reshape
класса Component.
В предыдущей главе мы уже использовали
один из подклассов Panel — Applet. Каждый раз, когда мы
создавали Applet, методы paint и update рисовали его
изображение на поверхности объекта Panel.
Прежде, чем мы углубимся в методы Panel, давайте
познакомимся с компонентом Canvas, который можно
вставлять в пустую Panel при работе с объектом Applet.
Canvas
Основная идея использования объектов
Canvas в том, что они являются семантически
свободными компонентами. Вы можете придать
объекту Canvas любое поведение и любой желаемый
внешний вид. Его имя
подразумевает, что этот класс
является пустым холстом, на котором вы можете
“нарисовать” любой компонент — такой, каким вы
его себе представляете.
Произведем от Canvas подкласс GrayCanvas,
который будет просто закрашивать себя серым
цветом определенной насыщенности. Наш апплет
будет создавать несколько таких объектов, каждый
со своей интенсивностью серого цвета.
/* <applet code = “PanelDemo”
width=300
height=300>
</applet>
*/
import java.awt.*;
import java.applet.*;
class GrayCanvas extends Canvas {
Color gray;
public GrayCanvas(float g) {
gray = new Color(g, g, g);
}
public void paint(Graphics g) {
Dimension size = size();
g.setColor(gray);
g.fillRect(0, 0, size.width, size.height);
g.setColor(Color.black);
g.drawRect(0, 0, size.width-1, size.height-1);
} }
public class PanelDemo extends Applet {
static final int n = 4;
public void init() {
setLayout(null);
int width = Integer.parseInt(getParameter("width"));
int height = Integer.parseInt(getParameter("height"));
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
float g = (i * n + j) / (float) (n * n);
Canvas с = new GrayCanvas(g);
add(c);
c.resize(width / n, height / n);
c.move(i * width / n, j * height / n);
}
}
} }
Вот как этот апплет выглядит на экране
–
PanelDemo.html. Мы устанавливаем размер
каждого из объектов Canvas на основе значения,
полученного с помощью метода size, который
возвращает объект класса Dimension. Обратите
внимание на то, что для размещения объектов Canvas в
нужные места используются методы resize и move. Такой
способ станет очень утомительным, когда мы
перейдем к более сложным компонентам и более
интересным вариантам расположения. А пока в
нашем апплете для выключения упомянутого
механизма использован вызов метода setLayout(null).
Label
Функциональность класса Label сводится к
тому, что он знает, как нарисовать объект String —
текстовую строку, выровняв ее нужным образом.
Шрифт и цвет, которыми отрисовывается строка
метки, являются частью базового определения
класса Component. Для работы с этими атрибутами
предусмотрены пары методов getFont/setFont и getForegroun
d/setForeground. Задать или
изменить текст строки после создания объекта с
помощью метода setText. Для задания режимов выравнивания в классе Label определены три
константы — LEFT, RIGHT и CENTER. Ниже приведен пример, в котором
создаются три метки, каждая — со своим режимом
выравнивания.
На этот раз, чтобы одновременно
переместить и изменить размер объектов Label, мы
использовали метод reshape. Ширина каждой из меток
равна полной ширине апплета, высота — 1/3 высоты
апплета. Вот как этот апплет должен выглядеть,
если его запустить
Объекты-кнопки помечаются строками,
причем эти строки нельзя выравнивать подобно
строкам объектов Label (они всегда центрируются
внутри кнопки
). Позднее в данной главе речь пойдет о том,
как нужно обрабатывать события, возникающие при
нажатии и отпускании пользователем кнопки. Ниже
приведен пример, в котором создаются три
расположенные по вертикали кнопки.
Класс Checkbox часто используется для
выбора одной из двух возможностей. При создании
объекта Checkbox ему передается текст метки и
логическое значение, чтобы задать исходное
состояние окошка с отметкой. Программно можно
получать и устанавливать состояние окошка с
отметкой с помощью методов getState и setState. Ниже
приведен пример с тремя объектами Checkbox,
задаваемое в этом примере исходное состояние
соответствует отметке в первом объекте.
Второй параметр конструктора Checkbox (в
предыдущем примере мы ставили там
null)
используется для
группирования нескольких объектов Checkbox. Для
этого сначала создается объект CheckboxGroup, затем он
передается в качестве параметра любому
количеству конструкторов Checkbox, при этом
предоставляемые этой группой варианты выбора
становятся взаимоисключающими (только один может быть
задействован). Предусмотрены и методы, которые позволяют
получить и установить группу, к которой
принадлежит конкретный объект Checkbox — getCheckboxGroup и
setCheckboxGroup. Вы можете пользоваться методами getCurrent
и setCurrent для получения и установки состояния
выбранного в данный момент объекта Checkbox. Ниже
приведен пример, отличающийся от предыдущего
тем, что теперь различные варианты выбора в нем
взаимно исключают друг друга.
Класс Choice (выбор) используется при
создании раскрывающихся списочных меню
(выпадающих списков типа
ComboBox в Windows). Компонент Choice занимает
ровно столько места, сколько требуется для
отображения выбранного в данный момент элемента,
когда пользователь щелкает мышью на нем,
раскрывается меню со всеми элементами, в котором
можно сделать выбор. Каждый элемент меню — это
строка, которая выводится, выровненная по левой
границе. Элементы меню выводятся в том порядке, в
котором они были добавлены в объект Choice. Метод
countItems возвращает количество пунктов в меню
выбора. Вы можете задать пункт, который выбран в
данный момент, с помощью метода select, передав ему
либо целый индекс (пункты меню перечисляются с
нуля), либо строку, которая совпадает с меткой
нужного пункта меню. Аналогично, с помощью
методов getSelectedItem и getSelectedIndex можно получить,
соответственно, строку-метку и индекс выбранного
в данный момент пункта меню. Вот очередной
простой пример, в котором создается два объекта Choice.
Класс List представляет собой
компактный список с возможностью выбора
нескольких вариантов и с прокруткой (аналог
ListBox в Windows). Ниже
приведен пример с двумя списками выбора, один из
которых допускает выбор нескольких элементов, а
второй — выбор единственного элемента.
Объекты Scrollbar (линейки прокрутки)
используются для выбора подмножества значений
между заданными минимумом и максимумом.
Визуально у линейки прокрутки есть несколько
органов управления, ориентированных либо
вертикально, либо горизонтально. Стрелки на
каждом из ее концов показывают, что, нажав на них,
вы можете продвинуться на один шаг в
соответствующем направлении. Текущее положение
отображается с помощью движка линейки прокрутки,
которым пользователь также может управлять,
устанавливая требуемое положение линейки.
Конструктор класса Scrollbar позволяет
задавать ориентацию линейки прокрутки — для
этого предусмотрены константы VERTICAL и HORIZONTAL.
Кроме того с помощью конструктора можно задать
начальное положение и размер движка, а так же
минимальное и максимальное значения, в пределах
которых линейка прокрутки может изменять
параметр. Для получения и установки текущего
состояния линейки прокрутки используются методы
getValue и setValue. Кроме того воспользовавшись
методами getMinimum и getMaximum, вы можете получить
рабочий диапазон объекта. Ниже приведен пример, в
котором создается и вертикальная, и
горизонтальная линейки прокрутки.
Класс TextField представляет собой
реализацию однострочной области для ввода
текста. Такие области часто используются в
формах для пользовательского ввода. Вы можете
“заморозить” содержимое объекта TextField с помощью
метода setEditable, а метод isEditable сообщит вам, можно ли
редактировать текст в данном объекте. Текущее
значение объекта можно получить методом getText
и установить
методом setText. С помощью метода select можно выбрать
фрагмент строки, задавая его начало и конец,
отсчитываемые с нуля. Для выбора всей строки
используется метод selectAll.
Метод setEchoChar задает символ, который
будет выводиться вместо любых вводимых символов.
Вы можете проверить, находится ли объект TextField в
этом режиме, с помощью метода echoCharIsSet, и узнать,
какой именно символ задан для эхо-печати, с
помощью метода getEchoChar.
Вот пример, в котором создаются
классические поля для имени пользователя и
пароля.
Обратите внимание, что в этом примере
мы заменили устаревший в
JDK 1.1 reshape
на setBounds
– TextFieldDemo.html. Вообще, в примерах
могут встречаться вызовы Deprecated API, за что автор приносит извинения
(после выхода Java 1.2, возможно, некоторые
устаревшие функции будут действительно удалены,
и тогда все примеры будут пересмотрены).
TextArea
Порой одной строки текста оказывается
недостаточно для конкретной задачи. AWT включает в
себя очень простой многострочный редактор
обычного текста, называемый TextArea. Конструктор
класса TextArea воспринимает значение типа String в
качестве начального текста объекта. Кроме того, в
конструкторе указывается число колонок и строк
текста, которые нужно выводить. Есть три метода,
которые позволяют программе модифицировать
содержимое объекта TextArea: appendText добавляет
параметр типа String в конец буфера; insertText вставляет
строку в заданное отсчитываемым от нуля индексом
место в буфере; rеplaceText копирует строку-параметр в
буфер, замещая ею текст, хранящийся в буфере
между первым и вторым параметрами-смещениями.
Ниже приведена программа, создающая объект TextArea
и вставляющая в него строку.
Все компоненты, с которыми мы работали
до сих пор в этой главе, размещались “вручную”. И
в каждом примере мы вызывали загадочный метод
setLayout(null). Этот вызов запрещал использование
предусмотренного по умолчанию механизма
управления размещением компонентов. Для решения
подобных задач в AWT предусмотрены диспетчеры
размещения (layout managers).
LayoutManager.
Каждый класс, реализующий интерфейс
LayoutManager, следит за списком компонентов, которые
хранятся с именами типа String. Всякий раз, когда вы
добавляете компонент в Panel, диспетчер размещения
уведомляется об этом. Если требуется изменить
размер объекта Panel, то идет обращение к
диспетчеру посредством методов minimumLayoutSize и
preferredLayoutSize. В каждом компоненте, который
приходится обрабатывать диспетчеру, должны
присутствовать реализации методов prefe
rredSize и minimumSize. Эти
методы должны возвращать предпочтительный и
минимальный размеры для прорисовки компонента,
соответственно. Диспетчер размещения по
возможности будет пытаться удовлетворить эти
запросы, в то же время заботясь о целостности
всей картины взаимного расположения
компонентов.
В Java есть несколько предопределенных классов —
диспетчеров размещения, описываемых ниже.
FlowLayout
Класс FlowLayout реализует простой стиль
размещения, при котором компоненты
располагаются, начиная с левого верхнего угла,
слева направо и сверху вниз. Если в данную строку
не помещается очередной компонент, он
располагается в левой позиции новой строки.
Справа, слева, сверху и снизу компоненты
отделяются друг от друга небольшими
промежутками. Ширину
этого промежутка можно задать в
конструкторе FlowLayout. Каждая строка с компонентами
выравнивается по левому или правому краю, либо
центрируется в зависимости от того, какая из
констант LEFT, RIGHT или CENTER была передана
конструктору. Режим выравнивания по умолчанию —
CENTER, используемая по умолчанию ширина промежутка
— 5 пикселей.
Ниже приведен пример, в котором в Panel
включается несколько компонентов Label. Объект Panel
использует FlowLayout с выравниванием RIGHT.
int width = Integer.parseInt(getParameter("width"));
int height = Integer.parseInt(getParameter("height"));
String val = "Data is not information " +
"is not knowledge is not wisdom.";
StringTokenizer st = new StringTokenizer(val);
while (st.hasMoreTokens()) {
add(new Button(st.nextToken()));
}
} }
Необходимо вызвать пример для двух
различных размеров
– FlowLayoutDemo1.html, FlowLayoutDemo2.html для того, чтобы
проиллюстрировать, как объекты Label перетекают из
строки в строку, и при этом строки выравниваются
по правому краю (или Вы можете изменять размеры
окнa appletViewer).
BorderLayout
Класс BorderLayout реализует обычный стиль
размещения для окон верхнего уровня, в котором
предусмотрено четыре узких компонента
фиксированной ширины по краям, и одна большая
область в центре, которая может расширяться и
сужаться в двух направлениях, занимая все
свободное пространство окна. У каждой из этих
областей есть строки-имена: String.North,
String.South, String.East и String.West
соответствуют четырем краям, a Center — центральной
области. Ниже приведен пример BorderLayout с
компонентом в каждой из названных областей.
Класс GridLayout размещает компоненты в
простой равномерной сетке. Конструктор этого
класса позволяет задавать количество строк и
столбцов. Ниже приведен пример, в котором GridLayout
используется для создания сетки 4х4, 15 квадратов
из 16 заполняются кнопками, помеченными
соответствующими индексами. Как вы уже, наверное,
поняли, это — панель для игры в “пятнашки”
Класс Insets используется для того, чтобы
вставлять в объект Panel границы, напоминающие
горизонтальные и вертикальные промежутки между
объектами, которые делает диспетчер размещения.
Для того, чтобы добиться вставки границ в объект
Panel, нужно заместить метод Insets реализацией,
возвращающей новый объект Insets с четырьмя целыми
значениями, соответствующими ширине верхнего,
нижнего, левого и правого краев.
public Insets insets() {
return new Insets(10, 10, 10, 10);
}
CardLayout
Класс CardLayout по своему уникален. Он
отличается от других программ управления
размещением компонентов тем, что представляет
несколько различных вариантов размещения,
которые можно сравнить с колодой карт. Колоду
можно тасовать так, чтобы в данный момент времени
наверху была только одна из карт. Это может быть
полезно при создании интерфейсов пользователя, в
которых есть необязательные компоненты,
включаемые и выключаемые динамически в
зависимости от реакции пользователя.
Window
Класс Window во многом напоминает Panel за
тем исключением, что он создает свое собственное
окно верхнего уровня. Большая часть
программистов скорее всего будет использовать
не непосредственно класс Window, а его подкласс Frame.
Frame
Frame — это как раз то, что обычно и
считают окном на рабочей поверхности экрана. У
объекта Frame есть строка с заголовком, управляющие
элементы для изменения размера и линейка меню.
Для того, чтобы вывести/спрятать изображение
объекта Frame, нужно использовать методы show и hide.
Ниже приведен пример апплета, который показывает
объект Frame с содержащимся в нем компонентом Tex
С каждым окном верхнего уровня может
быть связана линейка меню. Объект MenuBar может
включать в себя несколько объектов Menu. Последние,
в свою очередь, содержат в себе список вариантов
выбора — объектов MenuItem. Menu — подкласс MenuItem, так
что объекты Menu также могут включаться в этот
список, что позволяет создавать иерархически
вложенные подменю. Вот пример, в котором к окну
добавлены несколько вложенных меню.
AWT в своем нынешнем виде делает
прекрасную работу, являясь общим знаменателем -
библиотекой, единой для всех платформ. Некоторый
недостаток AWT в том, что, поскольку
каждый из AWT-компонентов
реализован на основе соответствующего
компонента базовой операционной системы, их
поведение и внешний вид может меняться при смене
платформы. Хорошо известны расширения и аналоги AWT – Swing, Java Foundation Classes (Netscape), Application Foundation Classes
(Microsoft).