Класс Container
Каждый компонент перед выводом на экран помещается в контейнер — подкласс класса Container.
Класс Container — прямой подкласс класса Component, и наследует все его методы. Кроме них основу класса составляют методы добавления компонентов в контейнер:
? add(Component comp) — компонент comp добавляется в конец контейнера;
? add(Component comp, int index) — компонент comp добавляется в позицию index в контейнере, если index == -1, то компонент добавляется в конец контейнера;
? add(Component comp, Object constraints) — менеджеру размещения контейнера даются указания объектом constraints;
? add(String name, Component comp) — компонент получает имя name.
Два метода удаляют компоненты из контейнера:
? remove(Component comp) удаляет компонент с именем comp;
? remove(int index) — удаляет компонент с индексом index в контейнере.
Один из компонентов в контейнере получает фокус ввода (input focus), на него направляется ввод с клавиатуры. Фокус можно переносить с одного компонента на другой и обратно клавишами <Tab> и <Shift>+<Tab>. Компонент способен запросить фокус методом requestFocus () и передать фокус следующему компоненту методом transferFocus (). Компонент может проверить, имеет ли он фокус, своим логическим методом hasFocus ( ). Это методы класса Component.
Для облегчения размещения компонентов в контейнере определяется менеджер размещения (layout manager)- объект, реализующий интерфейс LayoutManager или его
подынтерфейс LayoutManager2. Каждый менеджер размещает компоненты в каком-то своем порядке: один менеджер расставляет компоненты в таблицу, другой норовит растащить компоненты по сторонам, третий просто располагает их один за другим, как слова в тексте. Менеджер определяет смысл слов "добавить в конец контейнера" и "добавить в позицию index".
В контейнере в любой момент времени может быть установлен только один менеджер размещения. В каждом контейнере есть свой менеджер по умолчанию, установка другого менеджера производится методом
setLayout(LayoutManager manager);
Менеджеры размещения мы рассмотрим подробно в следующей главе. В данной главе будем размещать компоненты вручную, отключив менеджер по умолчанию методом
setLayout(null).
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при добавлении и удалении компонентов в контейнере происходит событие ContainerEvent.
Текстовая метка Label
Перейдем к рассмотрению конкретных компонентов. Самый простой компонент описывает класс Label.
Компонент Label — это строка текста, оформленная как графический компонент для размещения в контейнере. Текст можно поменять только методом доступа setText (String text), но не вводом пользователя с клавиатуры или с помощью мыши.
Создается объект этого класса одним из трех конструкторов:
? Label () — пустой объект без текста;
? Label (String text) — объект с текстом text, который прижимается к левому краю компонента;
? Label (String text, int alignment) — объект с текстом text и определенным размещением в компоненте текста, задаваемого одной из трех констант: center, left, right.
Размещение можно изменить методом доступа setAlignment(int alignment).
Остальные методы, кроме методов, унаследованных от класса Component, позволяют получить текст getText () и размещение getAlignment (), а также установить текст методами
setText(String) и размещение setAlignment(int).
События
В классе Label происходят события класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent.
Кнопка Button
Немногим сложнее класс Button.
Компонент Button — это кнопка стандартного для данной графической системы вида с надписью, умеющая реагировать на щелчок кнопки мыши — при нажатии она "вдавливается" в плоскость контейнера, при отпускании — становится "выпуклой".
Два конструктора, Button ( ) и Button (String label), создают кнопку без надписи и с надписью label соответственно.
Методы доступа getLabel() и setLabel(String label) позволяют получить и изменить надпись на кнопке.
Главная функция кнопки — реагировать на щелчки мыши, и прочие методы класса обрабатывают эти действия. Мы рассмотрим их в главе 15.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при воздействии на кнопку происходит событие ActionEvent.
Кнопка выбора Checkbox
Немного сложнее класса Button класс Checkbox, создающий кнопки выбора.
Компонент Checkbox — это кнопка с двумя состояниями. Графически она выглядит как надпись справа от небольшого квадратика, в котором в некоторых графических системах появляется галочка после щелчка кнопкой мыши — компонент переходит в состояние (state) on. После следующего щелчка галочка пропадает — это состояние off. В других графических системах состояние on отмечается "вдавливанием" квадратика. В компоненте Checkbox состояния on/off отмечаются логическими значениями true/false соответственно.
Три конструктора, Checkbox(), Checkbox(String label), Checkbox(String label, boolean state), создают компонент без надписи, с надписью label в состоянии off и в заданном состоянии state соответственно.
Методы доступа getLabel(), setLabel(String label), getState(), setState(boolean state) возвращают и изменяют эти параметры компонента.
Компоненты Checkbox удобны для быстрого и наглядного выбора из списка, целиком расположенного на экране, как показано на рис. 10.1. Там же продемонстрирована ситуация, в которой нужно выбрать только один пункт из нескольких. В таких ситуациях образуется группа так называемых радиокнопок (radio buttons). Они помечаются обычно кружком или ромбиком, а не квадратиком, выбор обозначается жирной точкой в кружке или "вдавливанием" ромбика.
События
В классе Checkbox происходят события класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, а при изменении состояния кнопки возникает событие ItemEvent.
Класс CheckboxGroup
В библиотеке AWT радиокнопки не образуют отдельный компонент. Вместо этого несколько компонентов Checkbox объединяются в группу с помощью объекта класса
CheckboxGroup.
Класс CheckboxGroup очень мал, поскольку его задача — просто дать общее имя всем объектам Checkbox, образующим одну группу. В него входит один конструктор по умолчанию CheckboxGroup () и два метода доступа:
? getSelectedCheckbox (), возвращающий выбранный объект Checkbox;
? setSelectedCheckbox(Checkbox box), задающий выбор.
Как создать группу радиокнопок
Чтобы организовать группу радиокнопок, надо сначала сформировать объект класса CheckboxGroup, а затем создавать кнопки конструкторами
Checkbox(String label, CheckboxGroup group, boolean state);
Checkbox(String label, boolean state, CheckboxGroup group);
Эти конструкторы идентичны, просто при записи конструктора можно не думать о порядке следования его параметров.
Только одна радиокнопка в группе может иметь состояние state == true.
Пора привести пример. В листинге 10.1 представлена программа, помещающая в контейнер Frame две метки Label сверху, под ними слева — три объекта Checkbox, справа — группу радиокнопок. Внизу — три кнопки Button. Результат выполнения программы показан на рис. 10.1.
Листинг 10.1. Размещение компонентов
import java.awt.*; import java.awt.event.*;
class SimpleComp extends Frame{
SimpleComp(String s){ super(s); setLayout(null);
Font f = new Font("Serif", Font.BOLD, 15); setFont(f);
Label l1 = new Label("Выберите товар:", Label.CENTER); l1.setBounds(10, 50, 120, 30); add(l1);
Label l2 = new Label("Выберите способ оплаты:"); l2.setBounds(160, 50, 200, 30); add(l2);
Checkbox ch1 = new CheckboxCR^™"); ch1.setBounds(20, 90, 100, 30); add(ch1);
Checkbox ch2 = new Checkbox("Диски"); ch2.setBounds(20, 120, 100, 30); add(ch2);
Checkbox ch3 = new Checkbox("Игрушки"); ch3.setBounds(20, 150, 100, 30); add(ch3);
CheckboxGroup grp = new CheckboxGroup();
Checkbox chg1 = new Checkbox("Почтовым переводом", grp, true); chg1.setBounds(170, 90, 200, 30); add(chg1);
Checkbox chg2 = new Checkbox("Кредитной картой", grp, false); chg2.setBounds(170, 120, 200, 30); add(chg2);
Button b1 = new Button("Продолжить"); b1.setBounds( 30, 220, 100, 30); add(b1);
Button b2 = new Button("Отменить");
b2.setBounds(140, 220, 100, 30); add(b2);
Button b3 = new Button^^M™"); b3.setBounds(250, 220, 100, 30); add(b3);
setSize(400, 300); setVisible(true);
}
public static void main(String[] args){
Frame f = new SimpleComp(" Простые компоненты"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0);
}
});
}
}
Заметьте, что каждый создаваемый компонент следует заносить в контейнер, в данном случае Frame, методом add (). Левый верхний угол компонента помещается в точку контейнера с координатами, указанными первыми двумя аргументами метода setBounds (). Размер компонента задается последними двумя параметрами этого метода.
Раскрывающийся список Choice
Если нет необходимости отображать весь список на экране, то вместо группы радиокнопок можно создать раскрывающийся список объект класса Choice.
Компонент Choice — это раскрывающийся список, один, выбранный, пункт (item) которого виден в поле, а другие появляются при щелчке кнопкой мыши на небольшой кнопке справа от поля компонента.
Вначале конструктором Choice () создается пустой список.
Затем методом add(string text) в список добавляются новые пункты с текстом text. Они располагаются в порядке написания методов add () и нумеруются от нуля.
Вставить новый пункт в нужное место можно методом insert(String text, int position). Выбор пункта можно произвести из программы методом select(String text) или
select(int position).
Удаление одного пункта из списка выполняется методом remove(String text) или
remove (int position), а всех пунктов сразу-методом removeAll().
Число пунктов в списке можно узнать методом getItemCount ( ).
Выяснить, какой пункт находится в позиции pos, можно методом getItem(int pos), возвращающим строку.
Наконец, определение выбранного пункта производится методом getSelectedIndex( ), возвращающим позицию этого пункта, или методом getSelectedItem(), возвращающим выделенную строку.
События
В классе Choice происходят события класса Component: ComponentEvent, FocusEvent, KeyEvent, MouseEvent, а при выборе пункта возникает событие ItemEvent.
Список List
Если надо показать на экране несколько пунктов списка, то создайте объект класса List.
Компонент List - это список с линейкой прокрутки, в котором можно выделить один или несколько пунктов. Количество видимых на экране пунктов определяется конструктором списка и размером компонента.
В классе три конструктора:
? List() — создает пустой список с четырьмя видимыми пунктами;
? List ( int rows ) -создает пустой список с rows видимыми пунктами;
? List (int rows, boolean multiple) - создает пустой список, в котором можно отме
тить несколько пунктов, если multiple == true.
После создания объекта в список добавляются пункты с текстом item:
? метод add (string item) — добавляет новый пункт в конец списка;
? метод add ( String item, int position) -добавляет новый пункт в позицию position.
Позиции нумеруются по порядку, начиная с нуля.
Удалить пункт можно методами remove(String item), remove(int position), removeAll().
Метод replaceItem(String newItem, int pos)позволяет заменить текст пункта в позиции
pos.
Количество пунктов в списке возвращает метод getItemCount ( ).
Выделенный пункт можно получить методом getSelectedItem(), а его позицию — методом getSelectedIndex().
Если список позволяет осуществить множественный выбор, то выделенные пункты в виде массива типа String[] можно получить методом getSelectedItems(), а позиции выделенных пунктов в виде массива типа int [ ] -методом getSelectedIndexes ( ).
Кроме этих необходимых методов класс List содержит множество других, позволяющих манипулировать пунктами списка и получать его характеристики.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при двойном щелчке кнопкой мыши на выбранном пункте происходит событие ActionEvent.
В листинге 10.2 с помощью классов Choice и List задаются компоненты, аналогичные компонентам листинга 10.1, а рис. 10.2 показывает, как изменится при этом интерфейс.
Листинг 10.2. Использование списков
import java.awt.*; import java.awt.event.*;
class ListTest extends Frame{
ListTest(String s){ super(s); setLayout(null);
setFont(new Font("Serif", Font.BOLD, 15));
Label l1 = new Label("Выберите товар:", Label.CENTER); l1.setBounds(10, 50, 120, 30); add(l1);
Label l2 = new Label("Выберите способ оплаты:");
l2.setBounds(170, 50, 200, 30); add(l2);
List l = new List(2, true); l.add("Книги"); l.add("Диски") ; l.add("Игрушки");
l.setBounds(20, 90, 100, 40); add(l);
Choice ch = new Choice(); ch.add("Почтовым переводом"); ch.add("Кредитной картой"); ch.setBounds(170, 90, 200,30); add(ch);
Button b1 = new Button("Продолжить"); b1.setBounds( 30, 150, 100, 30); add(b1);
Button b2 = new Button("Отменить");
b2.setBounds(140, 150, 100, 30); add(b2);
Button b3 = new ButtonC^bM™"); b3.setBounds(250, 150, 100, 30); add(b3);
setSize(400, 200); setVisible(true);
}
public static void main(String[] args){
Frame f = new ListTest(" Простые компоненты"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
Компоненты для ввода текста
В библиотеке AWT есть два компонента для ввода текста с клавиатуры: компонент TextField, позволяющий ввести только одну строку, и компонент TextArea, в который можно ввести множество строк.
Оба класса расширяют класс Textcomponent, в котором собраны их общие методы, такие как выделение текста, позиционирование курсора, получение текста.
Класс TextComponent
В классе Textcomponent нет конструктора, этот класс не используется самостоятельно.
Основной метод класса — getText () — возвращает в виде строки string текст, находящийся в поле ввода.
Поле ввода может быть нередактируемым, в этом состоянии текст в поле нельзя изменить с клавиатуры или мышью. Узнать состояние поля можно логическим методом
isEditable ( ), изменить значения в нем — методом setEditable(boolean editable).
Текст, находящийся в поле, хранится как объект класса string, поэтому у каждого символа есть индекс (у первого — индекс 0). Индекс используется для определения позиции курсора (caret) методом getCaretPosition (), для установки позиции курсора методом
setCaretPosition(int ind) и для выделения текста.
Текст выделяется, как обычно, мышью или клавишами со стрелками при нажатой клавише <Shift>, но можно выделить его из программы методом select(int begin, int end). При этом помечается текст от символа с индексом begin включительно до символа с индексом end исключительно.
Весь текст выделяет метод selectAll (). Можно отметить начало выделения методом
setSelectionStart(int ind);
и конец выделения методом
setSelectionEnd(int ind);
Важнее все-таки не задать, а получить выделенный текст. Его возвращает метод getSelectedText (), а начальный и конечный индекс выделения возвращают методы
getSelectionStart() и getSelectionEnd().
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении текста пользователем происходит событие TextEvent.
Строка ввода TextField
Компонент TextField — это поле для ввода одной строки текста. Ширина поля измеряется в колонках (column). Ширина колонки — это средняя ширина символа в шрифте, которым вводится текст. Нажатие клавиши <Enter> заканчивает ввод и служит сигналом к началу обработки введенного текста, т. е. при этом происходит событие
ActionEvent.
В классе четыре конструктора:
? TextField () — создает пустое поле шириной в одну колонку;
? TextField (int columns) -создает пустое поле с числом колонок columns;
? TextField(String text) — создает поле с текстом text;
? TextField (String text, int columns) - создает поле с текстом text и числом колонок
columns.
К методам, унаследованным от класса Textcomponent, добавляются методы getColumns () и
setColumns(int col).
Интересная разновидность строки ввода — строка для ввода пароля. В таком поле вместо вводимых символов появляется какой-нибудь особый эхо-символ, чаще всего звездочка, чтобы пароль никто не подсмотрел из-за плеча.
Строка ввода пароля получается из обычной строки ввода после выполнения метода setEchoChar(char echo). Аргумент echo — это символ, который будет появляться в поле. Проверить, установлен ли эхо-символ, можно логическим методом echoCharIsSet (); получить эхо-символ — методом getEchoChar ( ).
Чтобы вернуть строку ввода в обычное состояние, достаточно выполнить метод
setEchoChar(0).
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении текста пользователем происходит событие TextEvent, а при нажатии клавиши <Enter> — событие ActionEvent.
Поле ввода TextArea
Компонент TextArea — это область ввода с произвольным числом строк. Нажатие клавиши <Enter> просто переводит курсор в начало следующей строки. В области ввода могут быть установлены линейки прокрутки, одна или обе.
Основной конструктор класса,
TextArea(String text, int rows, int columns, int scrollbars);
создает область ввода с текстом text, числом видимых строк rows, числом колонок columns и заданием полос прокрутки scrollbars одной из четырех констант:
SCROLLBARS_NONE, SCROLLBARS_HORIZONTAL_ONLY, SCROLLBARS_VERTICAL_ONLY, SCROLLBARS_BOTH.
Остальные конструкторы задают некоторые параметры по умолчанию:
? TextArea (String text, int rows, int columns) -присутствуют обе полосы прокрутки;
? TextArea (int rows, int columns) — в поле пустая строка;
? TextArea(string text) — размеры устанавливает контейнер;
? TextArea ( ) -конструктор по умолчанию.
Среди методов класса TextArea наиболее важны:
? append (string text) — добавляет текст text в конец уже введенного текста;
? insert (String text, int pos) -вставляет текст в указанную позицию pos;
? replaceRange(String text, int begin, int end) — удаляет текст, начиная с индекса begin включительно по end исключительно, и помещает вместо него текст text.
Другие методы позволяют изменить и получить количество видимых строк.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении текста пользователем происходит событие TextEvent.
Рассмотрим пример. В листинге 10.3 создаются три поля (tf1, tf2, tf3 — для ввода имени пользователя, его пароля и заказа) и не редактируемая область ввода, в которой накапливается заказ. В поле ввода пароля tf2 появляется эхо-символ *. Результат показан на рис. 10.3.
Листинг 10.3. Поля ввода
import java.awt.*; import java.awt.event.*;
class TextTest extends Frame{
TextTest(String s){ super(s); setLayout(null);
setFont(new Font("Serif", Font.PLAIN, 14));
Label 11 = new ЬаЬе1("Ваше имя:", Label.RIGHT); l1.setBounds(20, 30, 70, 25); add(l1);
Label 12 = new Labe1("naponb:", Label.RIGHT);
l2.setBounds(20, 60, 70, 25); add(l2);
TextField tf1 = new TextField(30);
tf1.setBounds(100, 30, 160, 25); add(tf1);
TextField tf2 = new TextField(30); tf2.setBounds(100, 60, 160, 25); add(tf2); tf2.setEchoChar('*');
TextField tf3 = new TextFie1d("Введите сюда Ваш заказ", 30); tf3.setBounds(10, 100, 250, 30); add(tf3);
TextArea ta = new TextArea("Вaш заказ:", 5, 50,
TextArea.SCROLLBARS_NONE);
ta.setEditable(false); ta.setBounds(10, 150, 250, 140); add(ta);
Button b1 = new Вы^о^^риме^^"); b1.setBounds(280, 180, 100, 30); add(b1);
Button b2 = new Button("Отменить");
b2.setBounds(280, 220, 100, 30); add(b2);
Button b3 = new ButtonC'BbM™"); b3.setBounds(280, 260, 100, 30); add(b3);
setSize(400, 300); setVisible(true);
}
public static void main(String[] args){
Frame f = new TextTest(" Поля ввода"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
}
Линейка прокрутки Scrollbar
Компонент Scrollbar- это линейка прокрутки, но В библиотеке AWT класс Scrollbar
используется еще и для организации ползунка (slider). Объект может располагаться горизонтально или вертикально, обычно полосы прокрутки размещают внизу и справа.
Каждая линейка прокрутки охватывает некоторый диапазон значений и хранит текущее значение из этого диапазона. В линейке прокрутки есть пять элементов управления для перемещения по диапазону. Две стрелки на концах линейки вызывают перемещение на одну единицу (unit) в соответствующем направлении при щелчке на стрелке кнопкой мыши. Положение движка или бегунка (bubble, thumb) показывает текущее значение из диапазона и может его изменять при перемещении бегунка с помощью мыши. Два промежутка между движком и стрелками позволяют переместиться на один блок (block) щелчком кнопки мыши.
Смысл понятий "единица" и "блок" зависит от объекта, с которым работает полоса прокрутки. Например, для вертикальной полосы прокрутки при просмотре текста это может быть строка и страница или строка и абзац.
Методы работы с данным компонентом описаны в интерфейсе Adjustable, который реализован классом Scrollbar.
В классе scrollbar три конструктора:
? Scrollbar () — создает вертикальную полосу прокрутки с диапазоном 0—100, текущим значением 0 и блоком величиной в 10 единиц;
? Scrollbar (int orientation) - ориентация полосы прокрутки orientation задается од
ной из двух констант: HORIZONTAL или VERTICAL;
? Scrollbar(int orientation, int value, int visible, int min, int max) — задает, кроме ориентации, еще начальное значение value, размер блока visible, диапазон значений
min—max.
Аргумент visible определяет еще и длину движка — она устанавливается пропорционально диапазону значений и длине полосы прокрутки. Например, конструктор по умолчанию задаст длину движка равной 0,1 длины полосы прокрутки.
Основной метод класса — getValue () — возвращает значение текущего положения движка на полосе прокрутки. Остальные методы доступа позволяют узнать и изменить характеристики объекта, примеры их использования показаны в листинге 15.6.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении значения пользователем происходит событие AdjustmentEvent.
В листинге 10.4 создаются три вертикальные полосы прокрутки — красная, зеленая и синяя, позволяющие выбрать какое-нибудь значение соответствующего цвета в диапазоне 0—255, с начальным значением 127. Кроме них создается область, заполняемая получившимся цветом, и две кнопки. Линейки прокрутки, их заголовок и масштабные метки помещены в отдельный контейнер p типа Panel. Об этом чуть позже в данной главе.
Как все это выглядит, показано на рис. 10.4.
Листинг 10.4. Линейки прокрутки для выбора цвета
import java.awt.*; import java.awt.event.*;
class ScrollTest extends Frame{
Scrollbar sbRed = new Scrollbar(Scrollbar.VERTICAL, 127, 10, 0, 255) Scrollbar sbGreen = new Scrollbar(Scrollbar.VERTICAL, 127, 10, 0, 255) Scrollbar sbBlue = new Scrollbar(Scrollbar.VERTICAL, 127, 10, 0, 255)Color mixedColor = new Color(127, 127, 127);
Label lm = new Label();
Button b1 = new Button("Применить");
Button b2 = new Ви^опСОтменить");
ScrollTest(String s){ super(s); setLayout(null);
setFont(new Font("Serif", Font.BOLD, 15));
Panel p = new Panel(); p.setLayout(null);
p.setBounds(10,50, 150, 260); add(p);
Label lc = new ЬаЬе1("Подберите цвет"); lc.setBounds(20, 0, 120, 30); p.add(lc);
Label lmin = new Label("0", Label.RIGHT); lmin.setBounds(0, 30, 30, 30); p.add(lmin);
Label lmiddle = new Label("127", Label.RIGHT); lmiddle.setBounds(0, 120, 30, 30); p.add(lmiddle); Label lmax = new Label("255", Label.RIGHT); lmax.setBounds(0, 200, 30, 30); p.add(lmax);
sbRed.setBackground(Color.red); sbRed.setBounds(40, 30, 20, 200); p.add(sbRed);
sbGreen.setBackground(Color.green); sbGreen.setBounds(70, 30, 20, 200); p.add(sbGreen);
sbBlue.setBackground(Color.blue); sbBlue.setBounds(100, 30, 20, 200); p.add(sbBlue);
Label lp = new Label("Образец:"); lp.setBounds(250, 50, 120, 30); add(lp);
lm.setBackground(new Color(127, 127, 127)); lm.setBounds(220, 80, 120, 80); add(lm); b1.setBounds(240, 200, 100, 30); add(b1); b2.setBounds(240, 240, 100, 30); add(b2);
setSize(400, 300); setVisible(true);
}
public static void main(String[] args){
Frame f = new ScrollTest(" Выбор цвета"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0);
}
});
}
}
Контейнер Panel
В листинге 10.4 использован контейнер Panel. Рассмотрим возможности этого класса.
Контейнер Panel — это невидимый компонент графического интерфейса, служащий для объединения нескольких других компонентов в один объект типа Panel.
Класс Panel очень прост, но важен. В нем всего два конструктора:
? Panel ( ) -создает контейнер с менеджером размещения по умолчанию FlowLayout;
? Panel (LayoutManager layout) — создает контейнер с указанным менеджером размещения компонентов layout.
После создания контейнера в него добавляются компоненты унаследованным методом
add():
Panel p = new Panel(); p.add(comp1); p.add(comp2);
и т. д. Размещает компоненты в контейнере его менеджер размещения, о чем мы поговорим в следующей главе.
Контейнер Panel используется очень часто. Он удобен для создания группы компонентов.
В листинге 10.4 три полосы прокрутки вместе с заголовком Подберите цвет и масштабными метками 0, 127 и 255 образуют естественную группу. Если мы захотим переместить ее в другое место окна, нам придется переносить каждый из семи компонентов, входящих в указанную группу. При этом придется следить за тем, чтобы их взаимное положение не изменилось. Вместо этого мы создали панель p и разместили на ней все семь элементов. Метод setBounds () каждого из рассматриваемых компонентов указывает в данном случае положение и размер компонента в системе координат панели p, а не окна Frame. В окно мы поместили сразу целую панель, а не ее отдельные компоненты.
Теперь для перемещения всей группы компонентов достаточно переместить панель, и находящиеся на ней объекты автоматически передвинутся вместе с ней, не изменив своего взаимного положения.
Контейнер ScrollPane
Контейнер ScrollPane может содержать только один компонент, но зато такой, который не помещается целиком в окне. Контейнер обеспечивает средства прокрутки для просмотра большого компонента. В контейнере можно установить линейки прокрутки либо постоянно, константой SCROLLBARS_ALWAYS, либо так, чтобы они появлялись только при необходимости (если компонент действительно не помещается в окно) константой SCROLLBARS_AS_NEEDED.
Если линейки прокрутки не установлены, а это задает константа SCROLLBARS_NEVER, то перемещение компонента для просмотра нужно обеспечить из программы одним из методов setScrollPosition().
В классе два конструктора:
? ScrollPane () — создает контейнер, в котором полосы прокрутки появляются по необходимости;
? ScrollPane(int scrollbars) — создает контейнер, в котором появление линеек прокрутки задается одной из трех указанных ранее констант.
Конструкторы создают контейнер размером 100x100 пикселов, в дальнейшем можно изменить размер унаследованным методом setSize(int width, int height) .
Ограничение, заключающееся в том, что ScrollPane может содержать только один компонент, легко обходится. Всегда можно сделать этим единственным компонентом объект класса Panel, разместив на панели что угодно.
Среди методов класса интересны те, что позволяют прокручивать компонент в
ScrollPane:
? методы getHAdj ustable ( ) и getVAdj ustable ( ) возвращают положение линеек прокрутки в виде интерфейса Adjustable;
? метод getScrollPosition () показывает в виде объекта класса Point координаты (x, y) точки компонента, находящейся в левом верхнем углу панели ScrollPane;
? метод setScrollPosition(Point p) или setScrollPosition(int x, int y) прокручивает компонент в позицию (x, y) .
Контейнер Window
Контейнер Window — это пустое окно, без внутренних элементов: рамки, строки заголовка, строки меню, линеек прокрутки. Это просто прямоугольная область на экране. Окно типа Window самостоятельно, оно не содержится ни в каком контейнере, его не надо заносить в контейнер методом add (). Однако оно не связано с оконным менеджером графической системы. Следовательно, нельзя изменить его размеры, переместить в другое место экрана. Поэтому оно может быть создано только каким-нибудь уже существующим окном, владельцем (owner) или родителем (parent) окна Window. Когда окновладелец убирается с экрана, вместе с ним убирается и порожденное окно. Владелец окна указывается в конструкторе:
? Window (Frame f) — создает окно, владелец которого — фрейм f;
? Window (Window owner) — создает окно, владелец которого — уже имеющееся окно или подкласс класса Window.
Созданное конструктором окно не выводится на экран автоматически. Его следует отобразить методом setVisible(true). Убрать окно с экрана можно методом setVisible (false), а проверить, видно ли окно на экране, — логическим методом
isShowing().
Методами
setIconImage(Image icon);
setIconImages(List<? extends Image> icons);
можно задать один или несколько ярлыков для окна, а посмотреть их можно методом
List<Image> getIconImages();
Окно типа Window возможно использовать для создания всплывающих окон предупреждения, сообщения, подсказки. Для создания диалоговых окон есть подкласс Dialog, всплывающих меню — класс PopupMenu.
Видимое на экране окно выводится на передний план методом toFront () или, наоборот, помещается на задний план методом toBack ( ). Методом setAlwaysOnTop (true) можно дать указание графическому менеджеру всегда держать окно на переднем плане. Не все графические менеджеры могут выполнить это указание, поэтому такую возможность следует предварительно проверить логическим методом isAlwaysOnTopSupported (). Выполнение этого указания можно проверить логическим методом isAlwaysOnTop ( ).
Уничтожить окно, освободив занимаемые им ресурсы, можно методом dispose (). Менеджер размещения компонентов в окне по умолчанию — BorderLayout.
Окно создает свой экземпляр класса Toolkit, который можно получить методом
getToolkit() .
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении размеров окна, его перемещении или удалении с экрана, а также показе на экране происходит событие WindowEvent.
Контейнер Frame
Контейнер Frame — это полноценное готовое окно со строкой заголовка, в которую помещены кнопки контекстного меню, сворачивания окна в ярлык и разворачивания во весь экран и кнопка закрытия приложения. Заголовок окна записывается в конструкторе или методом setTitle(String title). Окно окружено рамкой. В него можно установить строку меню методом setMenuBar (MenuBar mb). Это мы обсудим в конце данной главы.
На кнопке контекстного меню в левой части строки заголовка изображена дымящаяся чашечка кофе — логотип Java. Вы можете установить там другое изображение методом setIconImage(Image icon), создав предварительно изображение icon в виде объекта класса Image. Как это сделать, объясняется в главе 20.
Все элементы окна Frame вычерчиваются графической оболочкой операционной системы по правилам этой оболочки. Окно Frame автоматически регистрируется в оконном менеджере графической оболочки и может перемещаться, менять размеры, сворачиваться в панель задач (task bar) с помощью мыши или клавиатуры, как "родное" окно операционной системы.
Создать окно типа Frame можно следующими конструкторами:
? Frame () — создает окно с пустой строкой заголовка;
? Frame(String title) — записывает аргумент title в строку заголовка;
? Frame(GraphicsConfiguration gc) -определяет конфигурацию окна параметром gc.
? Frame(String title, GraphicsConfiguration gc) — определяет строку заголовка title и конфигурацию окна параметром gc.
Методы класса Frame осуществляют доступ к элементам окна, но не забывайте о том, что класс Frame наследует около двухсот методов классов Component, Container и Window. В частности, наследуется менеджер размещения по умолчанию-BorderLayout.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении размеров окна, его перемещении или удалении с экрана, а также показе на экране происходит событие WindowEvent.
Программа листинга 10.5 создает два окна типа Frame, в которые помещаются строки — метки Label. При закрытии основного окна щелчком по соответствующей кнопке в строке заголовка или комбинацией клавиш <Alt>+<F4> выполнение программы завершается обращением к методу System.exit(0), и закрываются оба окна. При закрытии второго окна происходит обращение к методу dispose (), и закрывается только это окно.
Листинг 10.5. Создание двух окон
import java.awt.*; import java.awt.event.*;
class TwoFrames{
public static void main(String[] args){ Fr1 f1 = new Fr1(" Основное окно");
Fr2 f2 = new Fr2(" Второе окно");
}
}
class Fr1 extends Frame{
Fr1(String s){ super(s); setLayout(null);
Font f = new Font("Serif", Font.BOLD, 15); setFont(f);
Label l = new Label('^TO главное окно", Label.CENTER); l.setBounds(10, 30, 180, 30); add(l);
setSize(200, 100); setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
}
class Fr2 extends Frame{
Fr2(String s){ super(s); setLayout(null);
Font f = new Font("Serif", Font.BOLD, 15); setFont(f);
Label l = new Label('^TO второе окно", Label.CENTER); l.setBounds(10, 30, 180, 30); add(l);
setBounds(50, 50, 200, 100); setVisible(true);
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ dispose();
}
});
}
}
На рис. 10.5 представлен вывод этой программы. Взаимное положение окон определяется оконным менеджером операционной системы и может быть не таким, какое показано на рисунке.
Контейнер Dialog
Контейнер Dialog — это окно обычно фиксированного размера, предназначенное для ответа на сообщения приложения. Оно автоматически регистрируется в оконном менеджере графической оболочки, следовательно, его можно перемещать по экрану, менять его размеры. Но окно типа Dialog, как и его суперкласс — окно типа Window, — обязательно имеет родительское окно — владельца owner, который указывается в конструкторе.
Окно типа Dialog может быть модальным (modal), в котором надо обязательно выполнить все предписанные действия, иначе из окна нельзя будет выйти. Модальное окно блокирует родительское окно и, возможно, еще несколько окон, образующих область блокировки (scope of blocking). Заблокированные окна не могут получить фокус ввода и находятся на экране позади модального окна.
Есть четыре области блокировки, определяемые типом модальности (modality type). Тип модальности задается следующими константами вложенного перечисления
Dialog.ModalityType:
? modeless — отсутствие блокировки;
? document_modal — блокируются родительские окна, образующие один документ, причем под документом здесь понимаются все окна с общим предком (document root);
? appli cat I on_modal — блокируются родительские окна, относящиеся к одному приложению;
? toolkit_modal — блокируются родительские окна, относящиеся к одному экземпляру класса Toolkit.
Более подробное и точное описание типов модальности приведено в документе The AWT Modality, хранящемся в файле docs/api/java/awt/doc-files/Modality.html.
В классе Dialog определена константа default_modality_type, равная application_modal в Java SE 6. Она неявно применяется в конструкторах класса и методе setModal(true).
Графическая система, в которой открыто окно, может не отрабатывать все типы модальности, поэтому в класс Toolkit введен логический метод
isModalityTypeSupported(Dialog.ModalityType modalityType), которым можно проверить тот или иной тип modalityType.
Отдельные окна можно исключить из области блокировки. Для этого в класс Window введен метод setModalExclusionType(Dialog.ModalExclusionType excType), аргументом которого служат константы из перечисления Dialog.ModalExclusionType:
? no_exclude — отсутствие исключения;
? application_exclude — модальные окна, имеющие тип модальности application_modal, не могут блокировать это родительское окно;
? toolkit_exclude — модальные окна, имеющие тип модальности toolkit_modal, не могут блокировать это родительское окно.
Опять-таки графическая система, в которой открыто окно, может не отрабатывать все эти исключения, поэтому в класс Toolkit введен еще один логический метод —
isModalExclusionTypeSupported(Dialog.ModalExclusionType excType).
В классе Dialog более десяти конструкторов. Из них:
? Dialog(Dialog owner) — создает немодальное диалоговое окно типа MODELESS с пустой строкой заголовка;
? Dialog (Dialog owner, String title) - создает немодальное диалоговое окно типа
modeless со строкой заголовка title;
? Dialog(Dialog owner, String title, boolean modal) — создает диалоговое окно, которое будет модальным типа default_modality_type, если параметр modal == true;
? Dialog(Dialog owner, String title, boolean modal, GraphicConfiguration gc) — создает
диалоговое окно с конфигурацией, определяемой параметром gc.
Пять конструкторов аналогичны предыдущим, но создают диалоговые окна, принадлежащие окну типа Frame:
Dialog(Frame owner);
Dialog(Frame owner, String title);
Dialog(Frame owner, boolean modal);
Dialog(Frame owner, String title, boolean modal);
Dialog(Frame owner, String title, boolean modal, GraphicsConfiguration gc);
Еще у пяти конструкторов, аналогичных предыдущим, первый параметр — типа Window, а не Frame.
Среди методов класса Dialog интересны методы, связанные с модальностью: метод
Dialog.ModalityType getModalityType();
возвращает установленный для окна тип модальности, а метод
setModalityType(Dialog.ModalityType type);
меняет тип модальности. Если тип модальности type не отрабатывается графической системой, то устанавливается тип modeless. Во многих графических системах новый тип модальности вступит в силу только после того, как окно будет закрыто и вновь открыто.
В прежних версиях Java SE использовались методы isModal(), проверяющий состояние модальности, и setModal(boolean modal), меняющий это состояние. Эти методы сейчас не применяются, но оставлены в JDK для обратной совместимости со старыми программами.
События
Кроме событий класса Componen — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении размеров окна, его перемещении или удалении с экрана, а также появлении на экране происходит событие WindowEvent.
В листинге 10.6 создается модальное окно доступа, в которое вводится имя и пароль. Пока не будет сделан правильный ввод, другие действия невозможны. На рис. 10.6 показан вид этого окна.
Листинг 10.6. Модальное окно доступа
import java.awt.*; import java.awt.event.*;
class LoginWin extends Dialog{
LoginWin(Frame f, String s){ super(f, s, true); setLayout(null);
setFont(new Font("Serif", Font.PLAIN, 14));
Label l1 = new Label("Ваше имя:", Label.RIGHT);
11. setBounds(20, 30, 70, 25); add(l1);
Label l2 = new Label("Пароль:", Label.RIGHT);
12. setBounds(20, 60, 70, 25); add(l2);
TextField tf1 = new TextField(30); tf1.setBounds(100, 30, 160, 25); add(tf1);
TextField tf2 = new TextField(30); tf2.setBounds(100, 60, 160, 25); add(tf2); tf2.setEchoChar('*');
Button b1 = new Button("Применить"); b1.setBounds(50, 100, 100, 30); add(b1);
Button b2 = new Button("Отменить"); b2.setBounds(160, 100, 100, 30); add(b2);
setBounds(50, 50, 300, 150);
}
}
class DialogTest extends Frame{
DialogTest(String s){ super(s); setLayout(null); setSize(200, 100); setVisible(true);
Dialog d = new LoginWin(this, " Окно входа"); d.setVisible(true);
}
public static void main(String[] args){
Frame f = new DialogTest(" Окно-владелец"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0);
}
});
}
Контейнер FileDialog
Контейнер FileDialog — это модальное окно с владельцем типа Frame, содержащее стандартное окно выбора файла для открытия (константа load) или сохранения (константа save). Окна операционной системы создаются и помещаются в объект класса FileDialog автоматически.
В классе шесть конструкторов:
? FileDialog ( Frame owner) -создает окно с пустым заголовком для открытия файла;
? FileDialog(Frame owner, String title) — создает окно открытия файла с заголовком
title;
? FileDialog(Frame owner, String title, int mode) — создает окно открытия или сохранения документа; аргумент mode имеет два значения: окно открытия файла
FileDialog.LOAD и окно сохранения файла FileDialog.SAVE.
Остальные три конструктора аналогичны первым трем, только первый параметр у них типа Dialog, а не Frame.
Методы класса getDirectory( ) и getFile ( ) возвращают только выбранный каталог и имя файла в виде строки String. Загрузку или сохранение файла затем нужно производить методами классов ввода/вывода, как рассказано в главе 23. Там же приведены примеры использования класса FileDialog.
Можно установить начальный каталог для поиска файла и имя файла методами
setDirectory(String dir) и setFile(String fileName).
Вместо конкретного имени файла fileName можно написать его шаблон, например *.java (первые символы — звездочка и точка), тогда в окне будут видны только имена файлов, заканчивающиеся точкой и словом java.
Метод setFilenameFilter (FilenameFilter filter) устанавливает шаблон filter для имени выбираемого файла. В окне будут видны только имена файлов, подходящие под шаблон. Этот метод не реализован в Sun JDK на платформе MS Windows.
События
Кроме событий класса Component — ComponentEvent, FocusEvent, KeyEvent, MouseEvent — при изменении размеров окна, его перемещении или удалении с экрана, а также появлении на экране происходит событие WindowEvent.
Создание собственных компонентов
Создать собственный компонент, дополняющий свойства и методы уже существующих компонентов AWT, очень просто — надо лишь унаследовать свой класс от существующего класса Button, TextField или другого класса-компонента.
Если нужно скомбинировать несколько компонентов в один, новый, компонент, то достаточно расширить класс Panel, расположив компоненты на панели.
Если же требуется создать совершенно новый компонент, то AWT предлагает две возможности: создать "тяжелый" или "легкий" компонент. Для создания собственных "тяжелых" компонентов в библиотеке AWT есть класс Canvas — пустой компонент, для которого создается свой peer-объект графической системы.
Компонент Canvas
Компонент Canvas — это пустой компонент. Класс Canvas довольно прост — в нем чаще всего используется только конструктор по умолчанию Canvas () и пустая реализация метода paint(Graphics g).
Чтобы создать свой "тяжелый" компонент, необходимо расширить класс Canvas, дополнив его нужными полями и методами, и при необходимости переопределить метод
paint().
Например, как вы заметили, на стандартной кнопке Button можно написать только одну текстовую строку. Нельзя написать несколько строк или отобразить на кнопке рисунок. Создадим свой "тяжелый" компонент — кнопку с рисунком.
В листинге 10.7 кнопка с рисунком — это класс FlowerButton. Рисунок задается методом drawFlower(), а рисуется методом paint(). Метод paint(), кроме того, чертит по краям кнопки внизу и справа отрезки прямых, изображающих тень, отбрасываемую "выпуклой" кнопкой. При нажатии кнопки мыши на компоненте такие же отрезки чертятся вверху и слева — кнопка "вдавилась". При этом рисунок сдвигается на два пиксела вправо вниз — он "вдавливается" в плоскость окна.
Кроме этого, в классе FlowerButton задана реакция на нажатие и отпускание кнопки мыши. Это мы обсудим в главе 15, а пока скажем, что при каждом нажатии и отпускании кнопки меняется значение поля isDown и кнопка перечерчивается методом repaint (). Это достигается выполнением методов mousePressed () и mouseReleased ( ).
Для сравнения рядом помещена стандартная кнопка типа Button того же размера. Рисунок 10.7 демонстрирует вид этих кнопок.
Листинг 10.7. Кнопка с рисунком
import java.awt.*; import java.awt.event.*;
class FlowerButton extends Canvas implements MouseListener{ private boolean isDown=false;
public FlowerButton(){ super();
setBackground(Color.lightGray);
addMouseListener(this);
}
public void drawFlower(Graphics g, int x, int y, int w, int h){ g.drawOval(x + 2*w/5 — 6, y, w/5, w/5);
g.drawLine(x + w/2 — 6, y + w/5, x + w/2 — 6, y + h — 4); g.drawOval(x + 3*w/10 — 6, y + h/3 — 4, w/5, w/5); g.drawOval(x + w/2 — 6, y + h/3 — 4, w/5, w/5);
}
public void paint(Graphics g){
int w = getSize().width, h = getSize().height; if (isDown){
g.drawLine(0, 0, w — 1, 0); g.drawLine(1, 1, w — 1, 1); g.drawLine(0, 0, 0, h — 1); g.drawLine(1, 1, 1, h — 1); drawFlower(g, 8, 10, w, h);
}else{
g.drawLine(0, h — 2, . w — 2, h — 2) g.drawLine(1, h — 1, 1s:—i1s 1) g.drawLine(w — 2, h — 2, w — 2, 0) g.drawLine(w — 1, h — 1, w — 1, 1) drawFlower(g, 6, 8, w, h);}
}
public void mousePressed(MouseEvent e){ isDown=true; repaint();
}
public void mouseReleased(MouseEvent e){ isDown=false; repaint();
}
public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e) {} public void mouseClicked(MouseEvent e){}
}
class DrawButton extends Frame{ DrawButton(String s){ super(s); setLayout(null);
Button b = new Button("OK"); b.setBounds(200, 50, 100, 60); add(b);
FlowerButton d = new FlowerButton(); d.setBounds(50, 50, 100, 60); add(d);
setSize(400, 150); setVisible(true);
public static void main(String[] args){
Frame f = new DrawButton(" Кнопка с рисунком"); f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent ev){ System.exit(0);
}
});
}
}
Создание "легкого" компонента
"Легкий" компонент, не имеющий своего peer-объекта в графической системе, создается как прямое расширение класса Component или Container. При этом необходимо задать те действия, которые в "тяжелых" компонентах выполняет peer-объект.
Например, заменив в листинге 10.7 заголовок класса FlowerButton строкой
class FlowerButton extends Component implements MouseListener{
а затем перекомпилировав и выполнив программу, вы получите "легкую" кнопку, но увидите, что ее фон стал белым, потому что метод setBackground(Color.lightGray) не сработал.
Это объясняется тем, что теперь всю черную работу по изображению кнопки на экране выполняет не peer-двойник кнопки, а "тяжелый" контейнер, в котором расположена кнопка, в нашем случае — класс Frame. Контейнер же ничего не знает о том, что надо обратиться к методу setBackground (), он рисует только то, что записано в методе paint ( ). Придется убрать метод setBackground() из конструктора и заливать фон серым цветом вручную в методе paint (), как показано в листинге 10.8.
"Легкий" контейнер не умеет рисовать находящиеся в нем "легкие" компоненты, поэтому в конце метода paint () "легкого" контейнера нужно обратиться к методу paint () суперкласса:
super.paint(g);
Тогда рисованием займется "тяжелый" суперкласс-контейнер. Он нарисует и лежащий в нем "легкий" контейнер, и размещенные в контейнере "легкие" компоненты.
Совет
Завершайте метод paint() "легкого" контейнера обращением к методу paint () суперкласса.
Предпочтительный размер "тяжелого" компонента устанавливается peer-объектом, а для "легких" компонентов его надо задать явно, переопределив метод getPreferredSize(), иначе некоторые менеджеры размещения, например FlowLayout (), установят нулевой размер, и компонент не будет виден на экране.
СОВЕТ
Переопределяйте метод getPreferredSize ().
Интересная особенность "легких" компонентов — они изначально рисуются прозрачными, незакрашенная часть прямоугольного объекта не будет видна. Это позволяет создать компонент любой видимой формы. Листинг 10.8 показывает, как можно изменить метод paint () листинга 10.7 для создания круглой кнопки и задать дополнительные методы, а рис. 10.8 демонстрирует ее вид.
Листинг 10.8. Создание круглой кнопки
getSize().height;
// Диаметр круга // Сохраняем текущий цвет // Устанавливаем серый цвет ; // Заливаем круг серым цветом
// Восстанавливаем текущий цвет
public void paint(Graphics g){ int w = getSize().width, h int d = Math.min(w, h);
Color c = g.getColor(); g.setColor(Color.lightGray); g.fillArc(0, 0, d, d, 0, 360); g.setColor(c); if (isDown){
g.drawArc(0, 0, d, d, 43, 180)
g.drawArc(1, 1, d — 2, d — 2, 43, 180); drawFlower(g, 8, 10, d, d);
}else{
g.drawArc(0, 0, d, d, 229, 162); g.drawArc(1, 1, d — 2, d — 2, 225, 170); drawFlower(g, 6, 8, d, d);
}
}
public Dimension getPreferredSize(){ return new Dimension(30,30);
}
public Dimension getMinimumSize(){ return getPreferredSize();
}
public Dimension getMaximumSize(){ return getPreferredSize();
}
Сразу же надо дать еще одну рекомендацию. "Легкие" контейнеры не занимаются обработкой событий без специального указания. Поэтому в конструктор "легкого" компонента следует включить обращение к методу enableEvents () для каждого типа событий. В нашем примере в конструктор класса FlowerButton полезно добавить строку
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
на случай, если кнопка окажется в "легком" контейнере. Подробнее об этом мы поговорим в главе 15.
Упражнение
2. Создайте треугольную кнопку.
Создание меню
В контейнер типа Frame заложена возможность установки стандартной строки меню (menu bar), располагаемой ниже строки заголовка, как показано на рис. 10.9. Эта строка — объект класса MenuBar.
Все, что нужно сделать для установки строки меню в контейнере Frame, — это создать объект класса MenuBar и обратиться к методу setMenuBar ( ) :
Frame f = new Frame("Пример меню");
MenuBar mb = new MenuBar(); f.setMenuBar(mb);
Если имя mb не понадобится, можно совместить два последних обращения к методам:
f.setMenuBar(new MenuBar());
Разумеется, строка меню еще пуста и пункты меню не созданы.
Каждый элемент строки меню — выпадающее меню (drop-down menu ) — это объект класса Menu.
Создать эти объекты и занести их в строку меню ничуть не сложнее, чем создать строку меню:
Menu mFile = new Menu("Файл"); mb.add(mFile);
Menu mEdit = new Menu("Правка"); mb.add(mEdit);
Menu mView = new Menu("Вид"); mb.add(mView);
Menu mHelp = new Menu("Справка"); mb.setHelpMenu(mHelp);
и т. д. Элементы располагаются слева направо в порядке обращения к методам add (), как показано на рис. 10.9. Во многих графических системах принято меню Справка (Help) прижимать к правому краю строки меню. Это достигается обращением к методу setHelpMenu(), но фактическое положение меню Справка определяется графической оболочкой.
Затем определяем каждое выпадающее меню, создавая его пункты. Каждый пункт меню — это объект класса MenuItem. Схема его создания и добавления к меню точно такая же, как и самого меню:
MenuItem create = new MenuItem("Создать"); mFile.add(create);
MenuItem open = new MenuItem("Открыть..."); mFile.add(open);
и т. д. Пункты меню будут расположены сверху вниз в порядке обращения к методам
add().
Часто пункты меню объединяются в группы. Одна группа от другой отделяется горизонтальной чертой. На рис. 10.9 черта проведена между командами Открыть и Отправить. Эта черта создается методом addSeparator() класса Menu или определяется как пункт меню с надписью специального вида — дефисом:
mFile.add(new MenuItem("-"));
Интересно, что класс Menu расширяет класс MenuItem, а не наоборот. Это означает, что меню само является пунктом меню и позволяет задавать меню в качестве пункта другого меню, тем самым организуя вложенные подменю:
Menu send = new Menu("Отправить"); mFile.add(send);
Здесь меню send добавляется в меню mFile как один из его пунктов. Подменю send заполняется пунктами меню, как обычное меню.
Часто команды меню создаются для выбора из них каких-то возможностей, подобно компонентам Checkbox. Такие пункты можно выделить щелчком кнопки мыши или отменить выделение повторным щелчком. Эти команды — объекты класса
CheckboxMenuItem:
CheckboxMenuItem disk = new CheckboxMenuItem("Диск A:", true); send.add(disk);
send.add(new CheckboxMenuItem("Архив"));
и т. д.
Все, что получилось в результате перечисленных действий, показано на рис. 10.9.
Многие графические оболочки, но не MS Windows, позволяют создавать отсоединяемые (tear-off) меню, которые можно перемещать по экрану. Это указывается в конструкторе
Menu(String label, boolean tearOff);
Если tearOff == true и графическая оболочка умеет создавать отсоединяемое меню, то оно будет создано. В противном случае этот аргумент просто игнорируется.
Наконец, надо назначить действия командам меню. Команды меню типа MenuItem порождают события типа ActionEvent, поэтому нужно присоединить к ним объект класса-слушателя как к обычным компонентам, записав что-то вроде
create.addActionListener(new SomeActionEventHandler()); open.addActionListener(new AnotherActionEventHandler());
Пункты типа CheckboxMenuItem порождают события типа ItemEvent, поэтому надо обращаться к объекту-слушателю этого события:
disk.addItemListener(new SomeItemEventHandler());
Очень часто действия, записанные в командах меню, вызываются не только щелчком кнопки мыши, но и "горячими" клавишами-акселераторами (shortcut), действующими чаще всего при нажатой клавише <Ctrl>. На экране в пунктах меню, которым назначены "горячие" клавиши, появляются подсказки вида Ctrl+N, Ctrl+O. "Горячая" клавиша определяется объектом класса MenuShortcut и указывается в его конструкторе константой класса KeyEvent, например:
MenuShortcut keyCreate = new MenuShortcut(KeyEvent.VK N);
После этого "горячей" будет комбинация клавиш <Ctrl>+<N>. Затем полученный объект указывается в конструкторе класса MenuItem:
MenuItem create = new MenuItem("Создать", keyCreate);
Нажатие комбинации клавиш <Ctrl>+<N> будет вызывать окно создания. Эти действия, разумеется, можно совместить, например:
MenuItem open = new MenuItem("Открыть...", new MenuShortcut(KeyEvent.VK O));
Можно добавить еще нажатие клавиши <Shift>. Действие пункта меню будет вызываться нажатием комбинации клавиш <Shift>+<Ctrl>+<X>, если воспользоваться вторым конструктором:
MenuShortcut(int key, boolean useShift); с аргументом useShift == true.
В листинге 10.9 приведена полная программа рисования с обработкой событий. Ее объяснение отложим до главы 15. Результат работы программы показан на рис. 10.10.
Листинг 10.9. Программа рисования с меню
import java.awt.*; import java.awt.event.*;
public class MenuScribble extends Frame{ public MenuScribble(String s){ super(s);
ScrollPane pane = new ScrollPane();
pane.setSize(300, 300); add(pane, BorderLayout.CENTER);
Scribble scr = new Scribble(this, 500, 500); pane.add(scr);
MenuBar mb = new MenuBar(); setMenuBar(mb);
Menu f = new Menu^'J^m");
Menu v = new MenuC'B^") ; mb.add(f); mb.add(v);
MenuItem open = new MenuItem("OTKpbiTb...",
new MenuShortcut(KeyEvent.VK O)); MenuItem save = new MenuItem("CoxpaHHTb",
new MenuShortcut(KeyEvent.VK S)); MenuItem saveAs = new MenuItem("CoxpaHHTb как..."); MenuItem exit = new MenuItem(,,Выxод,,,
new MenuShortcut(KeyEvent.VK Q)) ; f.add(open); f.add(save); f.add(saveAs); f.addSeparator(); f.add(exit);
open.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ FileDialog fd = new FileDialog(new Frame(),
" Загрузить", FileDialog.LOAD); fd.setVisible(true);
}
});
saveAs.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ FileDialog fd = new FileDialog(new Frame(),
" Сохранить", FileDialog.SAVE); fd.setVisible(true);
}
});
exit.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ System.exit(0);
}
});
Menu c = new Menu("UBeT");
MenuItem clear = new MenuItem("Очистить",
new MenuShortcut(KeyEvent.VK D)); v.add(c); v.add(clear);
MenuItem red = new MenuItem("Красный");
MenuItem green = new MenuItem("3exeHbM");
MenuItem blue = new MenuItem("Синий");
MenuItem black = new MenuItem("4epHbM"); c.add(red); c.add(green); c.add(blue); c.add(black);
red.addActionListener(scr); green.addActionListener(scr); blue.addActionListener(scr); black.addActionListener(scr); clear.addActionListener(scr);
addWindowListener(new WinClose()); pack();
setVisible(true);
}
class WinClose extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
public static void main(String[] args){
new MenuScribble(" "Рисовалка" с меню");
}
}
class Scribble extends Component implements
ActionListener, MouseListener, MouseMotionListener{ protected int lastX, lastY, w, h; protected Color currColor = Color.black; protected Frame f;
public Scribble(Frame frame, int width, int height){ f = frame; w = width; h = height; enableEvents (AWTEvent.MOUSE_EVENT_MASK |
AWTEvent. MOUSE_MOTION_EVENT_MASK) ; addMouseListener(this); addMouseMotionListener(this);
}
public Dimension getPreferredSize(){ return new Dimension(w, h);
}
public void actionPerformed(ActionEvent event){
String s = event.getActionCommand();
if (s.equals("Очистить")) repaint();
else if (s.equals("Красный")) currColor = Color.red;
else if (s.equals("Зеленый")) currColor = Color.green;
else if (s.equals("Синий")) currColor = Color.blue;
else if (s.equals("Чeрный")) currColor = Color.black;
}
public void mousePressed(MouseEvent e){
return;
if ((e.getModifiers() & MouseEvent.BUTTON1 MASK) == 0) lastX = e.getX(); lastY = e.getY();
public void mouseDragged(MouseEvent e){
if ((e.getModifiers() & MouseEvent.BUTTON1 MASK) == 0) return; Graphics g = getGraphics(); g.setColor(currColor);
g.drawLine(lastX, lastY, e.getX(), e.getY());
lastX = e.getX(); lastY = e.getY();
}
public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseMoved(MouseEvent e){}
}
Всплывающее меню
Всплывающее меню (popup menu) появляется обычно при нажатии или отпускании правой или средней кнопки мыши и является контекстным (context) меню. Его команды зависят от компонента, на котором была нажата кнопка мыши. В языке Java всплывающее меню — объект класса PopupMenu. Этот класс расширяет класс Menu, следовательно, наследует все свойства меню и пункта меню MenuItem. Всплывающее меню присоединяется не к строке меню типа MenuBar или к меню типа Menu в качестве подменю, а к определенному компоненту. Для этого в классе Component есть метод
add(PopupMenu menu).
У некоторых компонентов, например TextField и TextArea, уже существует всплывающее меню. Подобные меню нельзя переопределить.
Присоединить всплывающее меню можно только к одному компоненту. Если надо использовать всплывающее меню с несколькими компонентами в контейнере, то его присоединяют к контейнеру, а нужный компонент определяют с помощью метода getComponent () класса MouseEvent, как показано в листинге 10.10.
Кроме унаследованных свойств и методов, в классе PopupMenu есть метод show(Component comp, int x, int y), показывающий всплывающее меню на экране так, что его левый верхний угол располагается в точке (x, y) в системе координат компонента comp. Чаще всего это компонент, на котором нажата кнопка мыши, возвращаемый методом getComponent (). Компонент comp должен быть внутри контейнера, к которому присоединено меню, иначе возникнет исключительная ситуация.
Всплывающее меню появляется в MS Windows при отпускании правой кнопки мыши, в Motif — при нажатии средней кнопки, а в других графических системах могут быть иные правила. Чтобы учесть эту разницу, в класс MouseEvent введен логический метод isPopupTrigger(), показывающий, что возникшее событие мыши вызывает появление всплывающего меню. Его нужно вызывать при возникновении всякого события мыши, чтобы проверять, не является ли оно сигналом к появлению всплывающего меню, т. е. обращению к методу show (). Было бы слишком неудобно включать такую проверку во все семь методов классов-слушателей событий мыши. Поэтому метод isPopupTrigger() лучше вызывать в методе processMouseEvent ().
Переделаем программу рисования из листинга 10.9, введя в класс Scribble всплывающее меню для выбора цвета рисования и очистки окна и изменив обработку событий мыши. Для простоты уберем строку меню, хотя ее можно было оставить. Результат показан в листинге 10.10, а на рис. 10.11 — вид всплывающего меню в MS Windows.
Листинг 10.10. Программа рисования со всплывающим меню
import java.awt.*; import java.awt.event.*;
public class PopupMenuScribble extends Frame{ public PopupMenuScribble(String s){ super(s);
ScrollPane pane = new ScrollPane();
pane.setSize(300, 300); add(pane, BorderLayout.CENTER);
Scribble scr = new Scribble(this, 500, 500); pane.add(scr);
addWindowListener(new WinClose()); pack();
setVisible(true);
}
class WinClose extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
public static void main(String[] args){
new PopupMenuScribble(" "Рисовалка" с всплывающим меню");
}
class Scribble extends Component implements ActionListener{ protected int lastX, lastY, w, h; protected Color currColor = Color.black; protected Frame f; protected PopupMenu c;
public Scribble(Frame frame, int width, int height){ f = frame; w = width; h = height; enableEvents (AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK) ;
c = new РорирМепи("Цвет");
add(c);
MenuItem clear = new Мепи^ешСОчистить",
new MenuShortcut(KeyEvent.VK D)); MenuItem red = new MenultemCKpacHbM");
MenuItem green = new MenuItem(,,Зеленый,,);
MenuItem blue = new MenuItem("Синий");
MenuItem black = new MenultemC'depHbM"); c.add(red); c.add(green); c.add(blue); c.add(black); c.addSeparator(); c.add(clear);
red.addActionListener(this); green.addActionListener(this); blue.addActionListener(this); black.addActionListener(this); clear.addActionListener(this);
}
public Dimension getPreferredSize(){ return new Dimension(w, h);
}
public void actionPerformed(ActionEvent event){
String s = event.getActionCommand();
if (s.equals("Очистить")) repaint();
else if (s.equals("Красный")) currColor = Color.red;
else if (s.equals("Зеленый")) currColor = Color.green;
else if (s.equalsCG^Hm")) currColor = Color.blue;
else if (s.equals("Черный")) currColor = Color.black;
}
public void processMouseEvent(MouseEvent e){ if (e.isPopupTrigger())
c.show(e.getComponent(), e.getX(), e.getY()); else if (e.getID() == MouseEvent.MOUSE_PRESSED){ lastX = e.getX(); lastY = e.getY();
}
else super.processMouseEvent(e);
}
public void processMouseMotionEvent(MouseEvent e){ if (e.getID() == MouseEvent.MOUSE_DRAGGED){
Graphics g = getGraphics();
g. setColor(currColor);
g.drawLine(lastX, lastY, e.getX(), e.getY()); lastX = e.getX(); lastY = e.getY();
}
else super.processMouseMotionEvent(e);
}
}
Вопросы для самопроверки
1. Почему класс Container наследует от класса Component, а не наоборот?
2. Каковы общие свойства всех компонентов?
3. Почему класс Component сделан абстрактным?
4. Почему надпись в контейнере — это целый компонент класса Label, а не просто строка символов?
5. Почему для группы радиокнопок не создан отдельный компонент?
6. В чем разница между текстовыми компонентами TextField и TextArea?
7. Почему мы всегда используем окно типа Frame, а не типа Window?
8. Чем отличается создание "тяжелого" компонента от создания "легкого" компонента?
ГЛАВА 1 1