Информатика программирование : Учебное пособие: Многопоточность и работа с потоками
Учебное пособие: Многопоточность и работа с потоками
Факультет "Информатика и системы управления"
Методические указания к лабораторной работе
по курсу
"Распределенные системы обработки информации"
Многопоточность
Москва 2004
г.
Оглавление
Цель работы.. 3
Задание для домашней подготовки. 3
Задание к лабораторной работе. 3
Содержание отчета. 4
Контрольные вопросы. 4
Литература. 6
Приложение 1. Графические примитивы. 7
Методы класса Graphics. 7
Преобразование координат. 21
Рисование фигур средствами
Java2D.. 25
Приложение 2. Манипуляции с курсорами. 31
Класс Cursor 31
Приложение 3. Обработка действий мыши и клавиатуры. 35
Обработка действий мыши. 35
Обработка действий клавиатуры.. 36
Приложение 4. Нити процессов. 38
Класс Thread и интерфейс Runnable. 39
Приложение 5. Согласование работы нескольких потоков. 43
Приложение 6. Пример программы «Бегущая строка». 44
Цель
работы
1.
Освоить пакет java.applet и класс java.applet.Applet.
2.
Научится
обрабатывать действия мыши и клавиатуры.
3.
Познакомиться с возможностями
пакетов java.awt.geom, java.awt и классов java.awt.Graphics и java.awt.Graphics2D.
4.
Изучить основные графические
примитивы.
5.
Освоить работу с
потоками.
6.
Применить
полученные знания на практике
Задание для домашней подготовки
Ознакомиться с теоретическим
материалом, представленным в приложениях к данным методическим указаниям и
примерами программ. Ознакомиться с текстом задания к лабораторной работе,
предложить тематику (сюжет) игры и функциональность, удовлетворяющую
требованиям задания к лабораторной работе, и написать программу.
Задание
к лабораторной работе
Разработать аплет, реализующий
игру, который должен содержать следующие элементы:
o
основа
пользовательского интерфейса — графические объекты, созданные на основе пакетов
java.awt, java.awt.geom и классов java.awt.Graphics и java.awt.Graphics2D.
o
не менее двух
движущихся объектов, созданных на основе тех же пакетов и классов. Каждый
объект должен управляется своим потоком, что должно быть визуально заметно (например,
с помощью задания разных значений в соответствующих методах Thread.sleep()).
Управление игрой должно
осуществляться с клавиатуры и/или мышью. Если используется только мышь, то нельзя
использовать курсор, заданный по умолчанию, (вместо него надо использовать
любой другой, подходящий по смыслу, или создать свой).
o
дополнительно (не
обязательно) – в игре может вестись подсчет очков.
В качестве возможного
варианта игры подойдет, например, «тир» или «футбол».
Содержание
отчета
Отчет должен содержать:
1.
Постановку
задачи, решаемой отлаженной программой.
2.
Руководство
пользователя отлаженной программы, содержащее описание интерфейсов всех функций
программы.
3.
Листинг программы
с необходимыми комментариями.
Контрольные
вопросы
1.
В случае, если
программа – аплет состоит из нескольких классов и после компиляции
сгенерировано несколько файлов *.class,
как внутри контейнера <applet></applet> html – файла это необходимо указать?
2.
Сколько классов в
пакете java.applet?
3.
Как получить
координаты центра окна?
4.
Что такое
интерлиньяж?
5.
Как задать форму
курсора?
6.
Назовите имя главного
потока в Java – программе (главный поток – это
поток, который запускает метод main()).
7.
По какому
принципу присваиваются имена потокам, чьи имена не задаются программистом явно?
8.
Каким образом
можно составить композицию аффинных преобразований, чтобы в дальнейшем применять
ее к объекту как единое преобразование.
Литература
Официальные источники:
1.
Кен
Арнольд, Джеймс Гослинг, Дэвид Холмс. Язык программирования Java™.
2.
Официальный
сайт Java — http://java.sun.com/ (есть раздел на русском языке с учебником).
3.
Java™ 2 SDK, Standard Edition Documentation — http://java.sun.com/products/jdk/1.5/index.html.
4.
Джеймс
Гослинг, Билл Джой, Гай Стил. Спецификация языка Java (The Java Language Specification — http://www.javasoft.com/docs/books/jls/). Перевод на русский язык
http://www.uni-vologda.ac.ru/java/jls/index.html
5.
Официальный
сайт проекта Eclipse — http://www.eclipse.org/.
Другое:
1.
Дмитрий
Рамодин. Начинаем программировать на языке Java.
2.
Николай
Смирнов. Java 2: Учебное пособие.
3.
Картузов
А. В. Программирование на языке Java.
4.
Вязовик
Н.А. Программирование на Java.
5.
Алексей
Литвинюк. Введение в интегрированную среду разработки Eclipse — http://lib.juga.ru/article/articleview/174/1/69/.
Приложение 1. Графические
примитивы
При создании
компонента автоматически формируется его графический контекст. В контексте
размещается область рисования и вывода текста и изображений. Контекст содержит
текущий и альтернативный цвет рисования и цвет фона — объекты класса Color, текущий шрифт для вывода текста
объект класса Font.
В контексте
определена система координат, начало которой с координатами (0, 0) расположено
в верхнем левом углу области рисования, ось Ох направлена вправо, ось Оу
вниз. Координаты измеряются в пикселях.
Управляет
контекстом класс Graphics или новый класс Graphics2D, введенный в Java 2.
Поскольку графический контекст сильно зависит от конкретной графической
платформы, эти классы сделаны абстрактными. Поэтому нельзя непосредственно
создать экземпляры класса Graphics или Graphics2D.
Однако каждая
виртуальная машина Java реализует методы этих классов, создает их экземпляры
для компонента и предоставляет объект класса Graphics методом getGraphics()
класса Component или как аргумент методов paint() и update().
Посмотрим
сначала, какие методы работы с графикой и текстом предоставляет класс Graphics.
Методы
класса Graphics
При создании
контекста в нем задается текущий цвет для рисования, обычно черный, и цвет фона
области рисования — белый или серый. Изменить текущий цвет можно методом setColor (Color
newColor), аргумент newColor которого — объект класса Color.
Узнать
текущий цвет можно методом getColor
(), возвращающим объект класса Color.
Как задать цвет
Цвет, как и
все в Java, — объект определенного класса, а именно, класса Color. Основу класса составляют семь
конструкторов цвета. Самый простой конструктор:
Color(int
red, int green, int blue)
создает цвет,
получающийся как смесь красной red, зеленой green и синей blue
составляющих. Эта цветовая модель называется RGB. Каждая составляющая меняется
от 0 (отсутствие составляющей) до 255 (полная интенсивность этой составляющей).
Например:
Color pureRed
= new Color(255, 0, 0);
Color
pureGreen = new Color(0, 255, 0);
определяют
чистый ярко-красный pureRed и чистый ярко-зеленый pureGreen цвета.
Во втором
конструкторе интенсивность составляющих можно изменять более гладко
вещественными числами от 0.0 (отсутствие составляющей) до 1.0 (полная
интенсивность составляющей):
Color(float
red, float green, float blue)
Например, Color someColor = new Color(O.OSf,
0.4f, 0.95f);
Третий
конструктор
Color(int
rgb)
задает все
три составляющие в одном целом числе. В битах 16—23 записывается красная
составляющая, в битах 8—15 — зеленая, а в битах 0—7 — синяя составляющая цвета.
Например, Color с = new
Color(OXFF8F48FF);
Здесь красная
составляющая задана с интенсивностью 0x8F, зеленая — 0x48, синяя — 0xFF.
Следующие три конструктора
Color(int
red, int green, int blue, int alpha)
Color(float
red, float green, float blue, float alpha)
Color(int
rgb, boolean hasAlpha)
вводят
четвертую составляющую цвета, так называемую "альфу", определяющую
прозрачность цвета. Эта составляющая проявляет себя при наложении одного цвета
на другой. Если альфа равна 255 или 1.0, то цвет совершенно непрозрачен,
предыдущий цвет не просвечивает сквозь него. Если альфа равна 0 или 0.0, то
цвет абсолютно прозрачен, для каждого пикселя виден только предыдущий цвет.
Последний из
этих конструкторов учитывает составляющую альфа, находящуюся в битах 24—31,
если параметр hasAlpha равен true.
Если же hasAlpha равно false, то составляющая
альфа считается равной 255, независимо от того, что записано в старших битах
параметра rgb.
Первые три
конструктора создают непрозрачный цвет с альфой, равной 255 или 1.0.
Седьмой конструктор
Color(ColorSpace
cspace, float[] components, float alpha)
позволяет
создавать цвет не только в цветовой модели (color model) RGB, но и в других
моделях: CMYK, HSB, CIEXYZ, определенных объектом класса ColorSpace.
Для создания
цвета в модели HSB можно воспользоваться статическим методом
getHSBColor(float
hue, float saturation, float brightness).
Если нет
необходимости тщательно подбирать цвета, то можно просто воспользоваться одной
из тринадцати статических констант типа Color, имеющихся в классе Color. Вопреки соглашению "Code Conventions" они
записываются строчными буквами: black, blue, cyan, darkGray, gray, green,
lightGray, magenta, orange, pink, red, white, yellow.
Методы класса
Color позволяют получить составляющие текущего цвета: getRed(), getGreen(), getBlue(),
getAlpha(), getRGB(), getColorSpace(), getComponents().
Два метода
создают более яркий brighter() и более темный darker() цвета по сравнению с
текущим цветом. Они полезны, если надо выделить активный компонент или,
наоборот, показать неактивный компонент бледнее остальных компонентов.
Два
статических метода возвращают цвет, преобразованный из цветовой модели RGB в
HSB и обратно:
float[]
RGBtoHSB(int red, int green, int blue, float[] hsb)
int
HSBtoRGB(int hue, int saturation, int brightness)
Создав цвет,
можно рисовать им в графическом контексте.
Как нарисовать
чертеж
Основной
метод рисования
drawLine(int
x1, int y1, int х2, int y2)
вычерчивает
текущим цветом отрезок прямой между точками с координатами (x1, y1) и (х2, у2).
Одного этого
метода достаточно, чтобы, нарисовать любую картину по точкам, вычерчивая каждую
точку с координатами (х, у) методом drawLine (x, у, х, у) и меняя цвета от
точки к точке. Но никто, разумеется, не станет этого делать.
Другие графические
примитивы:
·
drawRect(int x,
int у, int width, int height) — чертит прямоугольник со сторонами, параллельными краям экрана,
задаваемый координатами верхнего левого угла (х, у), шириной width пикселов и высотой height пикселов;
·
draw3DRect(int x,
int у, int width, int height, boolean raised) — чертит прямоугольник, как будто выделяющийся из
плоскости рисования, если аргумент raised
равен true, или как будто вдавленный в
плоскость, если аргумент raised равен false;
·
drawOval(int x,
int у, int width, int height) — чертит овал, вписанный в прямоугольник, заданный аргументами метода.
Если width == height, то получится окружность;
·
drawArc(int x,
int у, int width, int height, int startAngle, int arc) — чертит дугу овала, вписанного в
прямоугольник, заданный первыми четырьмя аргументами. Дуга имеет величину arc градусов и отсчитывается от угла startAngle. Угол отсчитывается в градусах от
оси Ох. Положительный угол отсчитывается против часовой стрелки, отрицательный
по часовой стрелке;
·
drawRoundRect
(int x, int у, int width, int height,
int arcWidth, int arcHeight) — чертит прямоугольник с закругленными краями. Закругления вычерчиваются
четвертинками овалов, вписанных в прямоугольники шириной arcwidth и высотой arcHeight, построенные в углах основного
прямоугольника;
·
drawPolyline(int[]
xPoints, int[] yPoints, int nPoints) — чертит ломаную с вершинами в точках (xPoints[i], ypoints[i]) и числом вершин nPoints;
·
drawPolygon(int[]
xPoints, int[] yPoints, int nPoints) — чертит ломаную, проводя замыкающий отрезок прямой между
первой и последней точкой;
·
drawFoiygon(Polygon
p) — чертит замкнутую
ломаную, вершины которой заданы объектом р
класса Polygon.
Класс Polygon
Этот класс
предназначен для работы с многоугольником, в частности, с треугольниками и
произвольными четырехугольниками.
Объекты этого
класса можно создать двумя конструкторами:
· Polygon () — создает пустой объект;
· Polygon(int[] xPoints, int[] yPoints,
int nPoints) — задаются
вершины многоугольника
(xPoints[i], yPoints[i]) и
их число nPoints
После
создания объекта в него можно добавлять вершины методом
addPoint(int
x, int у)
Логические
методы contains() позволяют проверить, не лежит ли в
многоугольнике заданная аргументами метода точка, отрезок прямой или целый
прямоугольник со сторонами, параллельными сторонам экрана.
Логические
методы intersects() позволяют проверить, не пересекается
ли с данным многоугольником отрезок прямой, заданный аргументами метода, или
прямоугольник со сторонами, параллельными сторонам экрана.
Методы getBounds() и getBounds2D() возвращают прямоугольник, целиком содержащий в себе данный
многоугольник.
Вернемся к
методам класса Graphics. Несколько методов вычерчивают
фигуры, залитые текущим цветом: fillRect(), fill3DRect(), fillArc(), fillOval(), fillPolygon(), fillRoundRect(). У них такие же аргументы, как и у
соответствующих методов, вычерчивающих незаполненные фигуры.
Например,
если вы хотите изменить цвет фона области рисования, то установите новый
текущий цвет и начертите им заполненный прямоугольник величиной во всю область:
public
void paint(Graphics g)(
Color
initColor = g.getColor(); // Сохраняем исходный цвет
g.setColor(new
Color(0, 0, 255)); // Устанавливаем цвет фона
// Заливаем
область рисования
g.fillRect(0, 0, getSize().width-1,
getSize().height-1);
g.setColor(initColor);
// Восстанавливаем исходный цвет
// Дальнейшие
действия
}
Как видно, в
классе Graphics собраны только самые необходимые средства рисования. Нет даже
метода, задающего цвет фона (хотя можно задать цвет фона компонента методом
setBackground() класса Сomponent). Средства рисования, вывода текста в область
рисования и вывода изображений значительно дополнены и расширены в подклассе
Graphics2D, входящем в систему Java 2D. Например, в нем есть метод задания цвета
фона setBackground(Color с).
Перед тем как
обратиться к классу Graphics2D, рассмотрим средства класса Graphics для вывода
текста.
Как вывести текст
Для вывода
текста в область рисования текущим цветом и шрифтом, начиная с точки (х, у), в,
классе Graphics есть несколько методов:
·
drawString
(String s, int x, int y) — выводит строку
s;
·
drawBytes(byte[]
b, int offset, int length, int x, int у) — выводит length элементов массива
байтов b, начиная
с индекса
offset;
·
drawChars(char[]
ch, int offset, int length, int x, int у) — выводит length элементов массива символов ch, начиная с индекса offset.
Четвертый
метод выводит текст, занесенный в объект класса, реализующего интерфейс
AttributedCharacterIterator. Это
позволяет задавать свой шрифт для каждого выводимого симвбла:
·
drawString(AttributedCharacterIterator iter, int x, int y).Точка
(х, у) — это левая нижняя точка первой буквы текста на базовой линии (baseline)
вывода шрифта.
Как установить
шрифт
Метод
setFont(Font newFont) класса Graphics устанавливает текущий шрифт для вывода
текста.
Метод getFont
() возвращает текущий шрифт.
Как и все в
языке Java, шрифт — это объект класса Font. Посмотрим, какие возможности
предоставляет этот класс.
Как задать шрифт
Объекты
класса Font хранят начертания (glyphs) символов, образующие шрифт. Их можно
создать двумя конструкторами:
·
Font (Map
attributes) — задает
шрифт с заданными аргументом attributes
атрибутами. Ключи атрибутов и некоторые их значения задаются константами класса
TextAttnbute из пакета java.awt.font. Этот конструктор характерен для Java 2D и
будет рассмотрен далее.
·
Font (String
name, int style, int size) — задает шрифт по имени name, со стилем style и размером size
типографских пунктов. Этот конструктор характерен для JDK 1.1, но широко
используется и в Java 2D в силу своей простоты.
Типографский
пункт в России и некоторых европейских странах равен 0,376 мм, Точнее, 1/72
части французского дюйма. В англо-американской системе мер пункт равен 1/72
части английского дюйма, 0,351 мм. Этот-то пункт и применяется в компьютерной
графике.
Имя шрифта name может быть строкой с физическим именем шрифта,
например, "Courier New", или одна из строк "Dialog",
"Dialoglnput",' "Monospaced", "Serif",
"SansSerif", "Symbol". Это так называемые логические имена
шрифтов (logical font names). Если name == null, то задается шрифт по умолчанию.
Стиль шрифта
style — это одна из констант класса Font:
·
BOLD
полужирный;
·
ITALIC — курсив;
·
PLAIN — обычный.
Полужирный
курсив (bolditalic) можно задать операцией побитового сложения, Font. BOLD |
Font. ITALIC.
При выводе
текста логическим именам шрифтов и стилям сопоставляются физические имена
шрифтов (font face name) или имена семейств шрифтов (font name). Это имена реальных
шрифтов, имеющихся в графической подсистеме операционной системы.
Например,
логическому имени "Serif" может быть сопоставлено имя семейства
(family) шрифтов Times New Roman, а в сочетании со стилями — конкретные
физические имена Times New Roman Bold, Times New Roman Italic. Эти шрифты
должны находиться в составе шрифтов графической системы той машины, на которой
выполняется приложение.
Список имен
доступных шрифтов можно просмотреть следующими операторами:
Font[]
fnt = Toolkit.getGraphicsEnvironment.getAHFonts();
for
(int i = 0; i< fnt.length; i++)
System.out.println(fnt[i].getFontName());
В состав SUN
J2SDK входит семейство шрифтов Lucida. Установив SDK, вы можете быть уверены,
что эти шрифты есть в вашей системе.
Таблицы
сопоставления логических и физических имен шрифтов находятся в файлах с именами
·
font.properties;
·
font.properties.ar;
·
font.properties.ja;
·
font.properties.ru.
и т. д. Эти
файлы должны быть расположены в JDK в каталоге jdkl.3\jre\lib или каком-либо
Другом подкаталоге lib корневого каталога JDK той машины, на которой
выполняется приложение.
Нужный файл
выбирается виртуальной машиной Java по окончании имени файла. Это окончание
совпадает с международным кодом языка, установленного в локали или в системном
свойстве user.language. Если у вас установлена русская локаль с международным
кодом языка "ru", то для сопоставления будет выбран файл
font.properties.ru. Если такой файл не найден, то применяется файл
font.properties, не соответствующий никакой конкретной локали.
Итак, собираясь
выводить строку str в графический контекст методом drawString(), мы создаем текущий шрифт конструктором класса Font,
указывая в нем логическое имя шрифта, например, "Serif". Исполняющая
система Java отыскивает в файле font.properties, соответствующем локальному
языку, сопоставленный этому логическому имени физический шрифт операционной
системы, например, Times New Roman. Если это Unicode-шрифт, то из него
извлекаются начертания символов строки str по их кодировке Unicode и
отображаются в графический контекст.
При выводе
строки в окно приложения очень часто возникает необходимость расположить ее
определенным образом относительно других элементов изображения: центрировать,
вывести над или под другим графическим объектом. Для этого надо знать метрику
строки: ее высоту и ширину. Для измерения размеров отдельных символов и строки
в целом разработан класс FontMetrics.
В Java 2D
класс FontMetrics заменен классом TextLayout. Его мы рассмотрим в конце этого приложения,
а сейчас выясним, какую пользу можно извлечь из методов класса FontMetrics.
Класс FontMetrics
Класс
FontMetrics является абстрактным, поэтому нельзя воспользоваться его
конструктором. Для получения объекта класса FontMetrics, содержащего набор
метрических характеристик шрифта f, надо обратиться к методу getFontMetrics (f)
класса Graphics или класса Component.
Класс
FontMetrics позволяет узнать ширину отдельного символа ch в пикселах методом
charWidth(ch), общую ширину всех символов
массива или полмассива символов или байтов методами getChars() и getBytes(), ширину целой строки str в пикселах
методом stringWidth(str).
Несколько
методов возвращают в пикселах вертикальные размеры шрифта.
Интерлиньяж
(leading) — расстояние между нижней точкой свисающих элементов таких букв, как
р, у и верхней точкой выступающих элементов таких букв, как б, в следующей
строке — возвращает метод getLeading().
Среднее
расстояние от базовой линии шрифта до верхней точки прописных букв и
выступающих элементов той же строки (ascent) возвращает метод getAscent(), а
максимальное — метод getMaxAscent().
Среднее
расстояние свисающих элементов от базовой линии той же строки (descent)
возвращает метод getDescent(), а максимальное — метод getMaxDescent().
Наконец,
высоту шрифта (height) — сумму ascent + descent + leading — возвращает метод
getHeight(). Высота шрифта равна расстоянию между базовыми линиями соседних
строк.
Эти элементы
показаны на рис. 1.
Рис. 1. Элементы шрифта
Дополнительные
характеристики шрифта можно определить методами класса LineMetrics из пакета
java.awt.font. Объект этого класса можно получить несколькими методами
getLineMetrics () класса FontMetrics.
Пример 1
показывает применение графических примитивов и шрифтов, а рис. 2 — результат
выполнения программы из этого примера.
Пример 1. Использование графических примитивов
и шрифтов
import
java.awt.*;
import
j ava.awt.event.*;
class
GraphTest extends Frame{
GraphTest(String
s) {
super(s);
setBounds(0,
0, 500, 300);
setVisible(true);
}
public
void paint(Graphics g){
Dimension
d = getSize();
int
dx=d.width/20, dy=d.height/20;
g.drawRect(dx,
dy+20, d.width-2*dx, d.height-2*dy-20);
g.drawRoundRect(2*dx,
2*dy+20, d.width-4*dx, d.height-4*dy-20, dx, dy);
g.fillArctd.width
(2-dx, d.height-2*dy+1, 2*dx, dy-1, 0, 360);
g.drawArctd.width
(2-3*dx, d.height-3*dy/2-5, dx, dy/2, 0, 360);
g.drawArctd.width
(2+2*dx, d.height-3*dy/2 - 5, dx, dy/2, 0, 360);
Font
fl = new Font("Serif", Font.BOLD(Font.ITALIC, 2*dy);
Font
f2 = new Font ("Serif", Font.BOLD, 5*dy/2);
FontMetrics
fml = getFontMetrics(fl);
FontMetrics
fm2 = getFontMetrics(f2);
String s1 =
"Всякая последняя ошибка";
String s2 =
"является предпоследней.";
String s3 =
"Закон отладки";
int
firstLine = d.height/3;
int
nextLine = fml.getHeight();
int
secondLine = firstLine+nextLine/2;
g.setFont(f2);
g.drawstring(s3,
(d.width-fm2.stringWidth(s3))/2, firstLine);
g.drawLine(d.width/4,
secondLine-2,
3*d.width/4,
secondLine-2);
g.drawLine(d.width/4,
secondLine—1, 3*d.width/4, secondLine-1);
g.drawLine(d.width/4,
secondLine, 3*d.width/4, secondLine);
g.setFont(fl);
g.drawstring(s1,(d.width-fml.stringWidth(s1))/2,
firstLine+2*nextLine);
g.drawString(s2,(d.width-fml.stringWidth(s2))/2, firstLine+3*nextLine);
}
public
static void main(String[] args){
GraphTest
f = new GraphTest("Пример рисования");
f.addWindowListener(new
WindowAdapter(){
public
void windowClosing(WindowEvent ev){
System.exit(0);
}
});
}
}
В примере 1
использован простой класс Dimension, главная задача которого — хранить ширину и
высоту прямоугольного объекта в своих полях width и height. Метод getSize() класса component возвращает
размеры компонента в виде объекта класса Dimension. В программе 1 размеры
компонента f типа GrapTest
установлены в конструкторе методом setBounds() равными 500x300 пикселов.
Еще одна
особенность примера 1 — для вычерчивания толстой линии, отделяющей заголовок от
текста, пришлось провести три параллельные прямые на расстоянии один пиксел
друг от друга.
Рис. 2. Пример использования класса Graphics
Как вы
увидели из обзора класса Graphics и сопутствующих ему классов, средства
рисования и вывода текста в этом классе весьма ограничены. Линии можно
проводить только сплошные и только толщиной в один пиксел, текст выводится
только горизонтально и слева направо, не учитываются особенности устройства
вывода, например, разрешение экрана.
Эти
ограничения можно обойти разными хитростями: чертить несколько параллельных
линий, прижатых друг к другу, как в примере 1, или узкий заполненный
прямоугольник, выводить текст по одной букве, получить разрешение экрана
методом getScreenSize() класса Java.awt.Toolkit и использовать его в
дальнейшем. Но все это затрудняет программирование, лишает его стройности и
естественности.
В Java 2
класс Graphics, в рамках системы Java 2D, значительно расширен классом
Graphics2D.
Возможности Java 2D
В систему
пакетов и классов Java 2D, основа которой— класс Graphics2D пакета java.awt,
внесено несколько принципиально новых положений:
·
Кроме координатной
системы, принятой в классе Graphics и названной координатным пространством
пользователя (User Space), введена еще система координат устройства вывода
(Device Space): экрана монитора, принтера. Методы класса Graphics2D
автоматически переводят (transform) систему координат пользователя в систему
координат устройства при выводе графики.
·
Преобразование
координат пользователя в координаты устройства можно задать
"вручную", причем преобразованием способно служить любое аффинное
преобразование плоскости, в частности, поворот на любой угол и/или
сжатие/растяжение. Оно определяется как объект класса AffineTransform. Его
можно установить как преобразование по умолчанию методом setTransform().
Возможно выполнять преобразование "на лету" методами transform() и
translate() и делать композицию преобразований методом concatenate().
·
Поскольку
аффинное преобразование вещественно, координаты задаются вещественными, а не
целыми числами.
·
Графические
примитивы: прямоугольник, овал, дуга и др., реализуют теперь новый интерфейс
shape пакета java.awt. Для их вычерчивания можно использовать новый единый для
всех фигур метод draw(), аргументом которого способен служить любой объект,
реализовавший интерфейс shape. Введен метод fill(), заполняющий фигуры— объекты
класса, реализовавшего интерфейс shape.
·
Для вычерчивания
(Stroke) линий введено понятие пера (реn). Свойства пера описывает интерфейс Stroke. Класс BasicStroke реализует этот интерфейс. Перо
обладает четырьмя характеристиками:
o
оно имеет толщину
(width) в один (по умолчанию) или несколько пикселов;
o
оно может
закончить линию (end cap) закруглением — статическая константа CAP_ROUND,
прямым обрезом — CAP_SQUARE (по умолчанию), или не фиксировать определенный
способ окончания — CAP_BUTT;
o
оно может сопрягать
линии (line joins) закруглением — статическая константа JOIN_ROOND, отрезком
прямой — JOIN_BEVEL, или просто состыковывать — JOIN_MITER (по умолчанию);
o
оно может чертить
линию различными пунктирами (dash) и штрих-пунктирами, длины штрихов и промежутков
задаются в массиве, элементы массива с четными индексами задают длину штриха, с
нечетными индексами — длину промежутка между штрихами.
·
Методы заполнения
фигур описаны в интерфейсе Paint. Три класса реализуют этот интерфейс. Класс Color реализует его сплошной (solid)
заливкой, класс GradientPaint — градиентным (gradient) заполнением, при котором
цвет плавно меняется от одной заданной точки к другой заданной точке, класс
TexturePaint — заполнением по предварительно
заданному образцу (pattern fill).
·
Буквы текста
понимаются как фигуры, т. е. объекты, реализующие интерфейс shape, и могут
вычерчиваться методом draw() с использованием всех возможностей этого метода.
При их вычерчивании применяется перо, все методы заполнения и преобразования.
·
Кроме имени,
стиля и размера, шрифт получил много дополнительных атрибутов, например,
преобразование координат, подчеркивание или перечеркивание текста, вывод текста
справа налево. Цвет текста и его фона являются теперь атрибутами самого текста,
а не графического контекста. Можно задать разную ширину символов шрифта,
надстрочные и подстрочные индексы. Атрибуты устанавливаются константами класса
TextAttribute.
·
Процесс
визуализации (rendering) регулируется правилами (hints), определенными константами
класса RenderingHints.
С такими
возможностями Java 2D стала полноценной системой рисования, вывода текста и
изображений. Посмотрим, как реализованы эти возможности, и как ими можно
воспользоваться.
Преобразование
координат
Правило
преобразования координат пользователя в координаты графического устройства
(transform) задается автоматически при создании графического контекста так же,
как цвет и шрифт. В дальнейшем его можно изменить методом setTransform() так
же, как меняется цвет или шрифт. Аргументом этого метода служит объект класса
AffineTransform из пакета java.awt.geom, подобно объектам класса Сolor или Font
при задании цвета или шрифта.
Рассмотрим
подробнее класс AffineTransform.
Класс
AffineTransform
Аффинное
преобразование координат задается двумя основными конструкторами класса
AffineTransform:
AffineTransform(double
a, double b, double с, double d, double e, double f)
AffineTransform
(float a, float b, float c, float d, float e, float f)
При этом
точка с координатами (х, у) в пространстве пользователя перейдет в точку с
координатами (а*х+с*у+е,
b*x+d*y+f) в
пространстве графического устройства.
Такое
преобразование не искривляет плоскость — прямые линии переходят в прямые, углы
между линиями сохраняются. Примерами аффинных преобразований служат повороты
вокруг любой точки на любой угол, параллельные сдвиги, отражения от осей,
сжатия и растяжения по осям.
Следующие два
конструктора используют в качестве аргумента массив {а, b, с, d, e, f} или {a, b, c, d}, если e = f
= 0, составленный из таких же коэффициентов в том же порядке:
AffineTransform(double[]
arr)
AffineTransform(float[]
arr)
Пятый
конструктор создает новый объект по другому, уже имеющемуся, объекту:
AffineTransform(AffineTransform
at)
Шестой
конструктор — конструктор по умолчанию — создает тождественное преобразование:
AffineTransform()
Эти
конструкторы математически точны, но неудобны при задании конкретных
преобразований, например, рассчитать коэффициенты поворота на 57° вокруг точки
с координатами (20, 40).
Во многих
случаях удобнее создать преобразование статическими методами, возвращающими
объект класса AffineTransform.
·
getRotateInstance (double angle) — возвращает поворот на угол angle, заданный в радианах, вокруг начала координат.
Положительное направление поворота таково, что точки оси Ох поворачиваются в
направлении к оси Оу. Если оси координат пользователя не менялись
преобразованием отражения, то положительное значение angle задает поворот по часовой стрелке.
·
getRotateInstance(double angle, double x,
double у) — такой же
поворот вокруг точки с координатами (х, у).
·
getScalelnstance
(double sx, double sy)
изменяет масштаб по оси Ох в sx раз, по оси Оу
в sy раз.
·
getSharelnstance(double
shx, double shy)
преобразует каждую точку (x,
у) в точку (x+shx*y, shy*x+y).
·
getTranslateInstance
(double tx, double ty)—сдвигает каждую
точку (х, у) в точку (x+tx, y+ty).
Метод createInverse() возвращает преобразование, обратное текущему преобразованию.
После
создания преобразования его можно изменить методами:
setTransform(AffineTransform
at)
setTransform(double
a, double b, double c, double d, double e, double f)
setToIdentity()
setToRotation(double
angle)
setToRotation(double
angle, double x, double y)
setToScale(double
sx, double sy)
setToShare(double
shx, double shy)
setToTranslate(double
tx, double ty)
сделав
текущим преобразование, заданное одним из этих методов.
Преобразования, заданные методами:
concatenate(AffineTransform
at)
rotate(double
angle)
rotate(double
angle, double x, double y)
scale(double
sx, double sy)
shear(double
shx, double shy)
translate(double
tx, double ty)
выполняются
перед текущим преобразованием, образуя композицию преобразований.
Преобразование,
заданное методом preConcatenate(AffineTransform
at), напротив, осуществляется после текущего преобразования.
Прочие методы
класса AffineTransform производят преобразования различных фигур в пространстве
пользователя.
Приведем
пример. Добавим в начало метода paint() в примере 1 четыре оператора, как
записано в примере 2.
Пример 2. Преобразование пространства
пользователя
// Начало примера
1...
public
void paint(Graphics gr){
Graphics2D
g = (Graphics2D)gr;
AffineTransform
at =
AffineTransform.getRotatelnstance(-Math.PI/4.0,
250.0,150.0);
at.concatenate(new
AffineTransform(0.5, 0.0, 0.0, 0.5, 100.0, 60.0));
g.setTransform(at);
Dimension
d = getSize();
//
Продолжение примера 1...
Метод paint()
начинается с получения экземпляра gr класса
Graphics2D простым приведением аргумента gr к
типу Graphics2D. Затем, методом getRotatelnstance() определяется поворот на 45°
против часовой стрелки вокруг точки (250.0, 150.0). Это преобразование
экземпляр at класса AffineTransform. Метод
concatenate(), выполняемый объектом at,
добавляет к этому преобразованию сжатие в два раза по обеим осям координат и
перенос начала координат в точку (100.0, 60.0). Наконец, композиция этих
преобразований устанавливается как текущее преобразование объекта g методом setTransform().
Преобразование
выполняется в следующем порядке. Сначала пространство пользователя сжимается в
два раза вдоль обеих осей, затем начало координат пользователя — левый верхний
угол — переносится в точку (100.0, 60.0) пространства графического устройства.
Потом картинка поворачивается на угол 45° против часовой стрелки вокруг точки
(250.0, 150.0).
Результат
этих преобразований показан на рис. 3.
Рис. 3. Преобразование координат
Рисование
фигур средствами Java2D
Характеристики
пера для рисования фигур описаны в интерфейсе Stroke. В Java 2D есть пока
только один класс, реализующий этот интерфейс — класс BasicStroke.
Класс
BasicStroke
Конструкторы
класса BasicStroke определяют характеристики пера. Основной конструктор
BasicStroke(float
width, int cap, int join, float miter, float[] dash, float dashBegin)
задает:
·
толщину пера
width в пикселах;
·
оформление конца
линии cap; это одна из констант:
o
CAP_ROUND
закругленный конец линии;
o
CAP_SQUARE
квадратный конец линии;
o
CAP_BUTT
оформление отсутствует;
·
способ сопряжения
линий join; это одна из констант:
o
JOIN_ROUND
линии сопрягаются дугой окружности;
o
JOIN_BEVEL
линии сопрягаются отрезком прямой, перпендикулярным биссектрисе угла между
линиями;
o
JOIN_MITER
линии просто стыкуются;
·
расстояние между
линиями miter, начиная с которого применяется сопряжение JOIN_MITER;
·
длину штрихов и
промежутков между штрихами — массив dash; элементы массива с четными индексами
задают длину штриха в пикселах, элементы с нечетными индексами — длину
промежутка; массив перебирается циклически;
·
индекс dashBegin,
начиная с которого перебираются элементы массива
·
dash.
Остальные
конструкторы задают некоторые характеристики по умолчанию:
·
BasicStroke
(float width, int cap, int join, float miter) — сплошная линия;
·
BasicStroke
(float width, int cap, int join) — сплошная линия с сопряжением JOIN_ROUND или JOIN_BEVEL; для сопряжения JOIN_MITER задается значение miter = 10.0f;
·
BasicStroke
(float width) — прямой обрез CAP_SQUARE и сопряжение JOIN_MITER со значением miter = 10.0f;
·
BasicStroke ()
ширина1. 0f.
В примере 3
определено пять перьев с разными характеристиками, рис 4 показывает, как они
рисуют.
Пример 3. Определение перьев
import
java.awt.*;
import
java.awt.geom. *;
import
java.awt.event.*;
class
StrokeTest extends Frame{
StrokeTest(String
s) {
super(s)
;
setSize(500,
400);
setvisible(true);
addWindowListener(new
WindowAdapter(){
public
void windowClosing(WindowEvent ev)(
System.exit(0);
}
});
}
public
void paint(Graphics gr){
Graphics2D
g = (Graphics2D)gr;
g.setFont(new
Font("Serif", Font.PLAIN, 15));
BasicStroke
pen1 = new BasicStroke(20, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,30);
BasicStroke
pen2 = new BasicStroke(20, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
BasicStroke
реnЗ = new BasicStroke(20,
BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_BEVEL);
floatf]
dash1 = {5, 20};
BasicStroke
pen4 = new BasicStroke(10, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_BEVEL,
10, dashl, 0);
float[]
dash2 = (10, 5, 5, 5};
BasicStroke
pen5 = new BasicStroke(10, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10,
dash2, 0);
g.setStroke(pen1);
g.draw(new
Rectangle2D.Double(50, 50, 50, 50));
g.draw(new
Line2D.Double(50, 180, 150, 180));
g.setStroke(pen2);
g.draw(new
Rectangle2D.Double(200, 50, 50, 50));
g.draw(new
Line2D.Double(50, 230, 150, 230));
g.setStroke(реn3);
g.draw(new
Rectangle2D.Double(350, 50, 50, 50));
g.draw(new
Line2D.Double(50, 280, 150, 280));
g.drawstring("JOIN_MITER",
40, 130);
g.drawstring("JOIN_ROUND",
180, 130);
g.drawstring("JOINJBEVEL",
330, 130);
g.drawstring("CAP_BUTT",
170, 190);
g.drawstring("CAP_ROUND",
170, 240);
g.drawstring("CAP_SQUARE",
170, 290);
g.setStroke(pen5);
g.drawfnew
Line2D.Double(50, 330, 250, 330));
g.setStroke(pen4);
g.draw(new
Line2D.Double(50, 360, 250, 360));
g.drawString("{10,
5, 5, 5,...}", 260, 335);
g.drawstring("(5,
10,...)", 260, 365);
}
public
static void main(String[] args){
new
StrokeTest("Моя программа");
}
}
Рис. 4. Перья с различными характеристиками.
После
создания пера одним из конструкторов и установки пера методом setStroke() можно
рисовать различные фигуры методами draw() и fill().
Общие
свойства фигур, которые можно нарисовать методом draw() класса Graphics2D,
описаны в интерфейсе shape. Этот интерфейс реализован для создания обычного
набора фигур — прямоугольников, прямых, эллипсов, дуг, точек — классами
Rectangle2D, RoundRectangle2D, Line2D, Ellipse2D, Arc2D, Point2D пакета java.awt.geom. В этом пакете есть еще классы CubicCurve2D и QuadCurve2D для создания
кривых третьего и второго порядка.
Все эти
классы абстрактные, но существуют их реализации — вложенные классы Double и
Float для задания координат числами соответствующего типа. В примере 3 использованы
классы Rectangle2D.Double И Line2D.Double
для вычерчивания прямоугольников и отрезков.
Класс
GeneralPath
Объекты
класса java.awt.geom.GeneralPath могут содержать сложные конструкции,
составленные из отрезков прямых или кривых линий и прочих фигур, соединенных
или не соединенных между собой. Более того, поскольку этот класс реализует
интерфейс shape, его экземпляры сами являются фигурами и могут быть элементами
других объектов класса GeneralPath.
Вначале
создается пустой объект класса GeneralPath конструктором по умолчанию
GeneralPath() или объект, содержащий одну фигуру, конструктором GeneralPath
(Shape sh).
Затем к этому
объекту добавляются фигуры методом append (Shape sh, boolean connect)
Если параметр
connect равен true, то новая фигура соединяется с предыдущими фигурами с
помощью текущего пера.
В объекте
есть текущая точка. Вначале ее координаты (0, 0), затем ее можно переместить в
точку (х, у) методом moveTo (float x, float у).
От текущей
точки к точке (х, у) можно провести:
·
отрезок прямой
методом lineTo(float x, float у);
·
отрезок
квадратичной кривой методом quadTo (float xi, float yl, float x, float y),
·
кривую Безье методом curveTo(float xl, float yl, float
x2, float y2, float x, float y).
Текущей
точкой после этого становится точка (х, у). Начальную и конечную точки можно
соединить методом closePath(). Вот
как можно создать треугольник с заданными вершинами:
GeneralPath p
= new GeneralPath();
p.moveTo(xl,
yl); // Переносим текущую точку в первую вершину,
p.lineTo(x2,
y2); // проводим сторону треугольника до второй вершины,
p.lineTo(x3,
уЗ); // проводим вторую сторону,
p.closePath();
// проводим третью сторону до первой вершины
Способы
заполнения фигур определены в интерфейсе Paint. В настоящее время Java 2D
содержит три реализации этого интерфейса — классы Color, GradientPaint и TexturePamt.
Приложение 2. Манипуляции с
курсорами
Класс Cursor
Основа класса
статические константы, определяющие форму курсора:
·
CROSSHAIR_CURSOR — курсор в виде креста, появляется
обычно при поиске позиции для размещения какого-то элемента;
·
DEFAULT_CURSOR — обычная форма курсора — стрелка
влево вверх;
·
HAND_CURSOR — "указующий перст",
появляется обычно при выборе какого-то элемента списка;
·
MOVE_CURSOR — крест со стрелками, возникает
обычно при перемещении элемента;
·
TEXT_CURSOR — вертикальная черта, появляется в
текстовых полях;
·
WAIT_CURSOR — изображение часов, появляется при
ожидании.
Следующие
курсоры появляются обычно при приближении к краю или углу компонента:
·
E_RESIZE_CURSOR — стрелка вправо с упором;
·
N_RESIZE_CURSOR — стрелка вверх с упором;
·
NE_RESIZE_CURSOR — стрелка вправо вверх, упирающаяся
в угол;
·
NW_RESIZE_CURSOR — стрелка влево вверх, упирающаяся в
угол;
·
S_RESIZE_CURSOR — стрелка вниз с упором;
·
SE_RESIZE_CURSOR — стрелка вправо вниз, упирающаяся в
угол;
·
SW_RESIZE_CURSOR — стрелка влево вниз, упирающаяся в
угол;
·
W_RESIZE_CURSOR — стрелка влево с упором.
Перечисленные
константы являются аргументом type в
конструкторе класса Cursor(int type).
Вместо
конструктора можно обратиться к статическому методу getPredefinedCursor(int
type), создающему объект класса Cursor
и возвращающему ссылку на него.
Получить
курсор по умолчанию можно статическим методом getDefauitcursor(). Затем
созданный курсор надо установить в компонент. Например, после выполнения:
Cursor
curs = new Cursor(Cursor.WAIT_CURSOR);
someComp.setCursor(curs);
при появлении
указателя мыши в компоненте somecomp указатель
примет вид часов.
Как создать свой курсор
Кроме этих
предопределенных курсоров можно задать свою собственную форму курсора. Ее тип
носит название CUSTOM_CURSOR. Сформировать свой курсор можно методом
createCustomCursor(Image
cursor, Point hotspot, String name)
создающим
объект класса Cursor и возвращающим ссылку на него.
Перед этим следует создать изображение курсора cursor — объект класса Image.
Аргумент name задает имя курсора, можно написать
просто null. Аргумент hotspot задает точку фокуса курсора. Эта точка должна быть в
пределах изображения курсора, точнее, в пределах, показываемых методом
getBestCursorSize(int
desiredWidth, int desiredHeight)
возвращающим
ссылку на объект класса Dimension. Аргументы метода означают желаемый размер
курсора. Если графическая система не допускает создание курсоров, возвращается
(0, 0). Этот метод показывает приблизительно размер того курсора, который
создаст графическая система, например, (32, 32). Изображение cursor будет подогнано под этот размер, при этом возможны
искажения.
Третий метод
getMaximumCursorColors() — возвращает наибольшее количество цветов, например,
256, которое можно использовать в изображении курсора.
Это методы
класса java.awt.Toolkit, с которым мы еще не работали. Класс Toolkit содержит
некоторые методы, связывающие приложение Java со средствами платформы, на
которой выполняется приложение. Поэтому нельзя создать экземпляр класса Toolkit
конструктором, для его получения следует выполнить статический метод
Toolkit.getDefaultToolkit().
Если
приложение работает в окне Window
или его расширениях, например, Frame, то можно получить экземпляр Toolkit
методом getToolkit() класса Window.
Соберем все
это вместе:
Toolkit tk =
Toolkit.getDefaultToolkit();
int colorMax
= tk.getMaximumCursorColors(); // Наибольшее число цветов
Dimension d =
tk.getBestCursorSize(50, 50); // d — размер изображения
int
w = d.width, h = d.height, k = 0;
Point
p = new Point(0, 0); // Фокус курсора
будет
// в его
верхнем левом углу
int[] pix =
new int[w * h]; // Здесь будут пикселы
//изображения
for(int
i = 0; i < w; i++)
for(int
j = 0; j < h; j++)
if
(j < i) pix[k++] = 0xFFFF0000; // Левый нижний угол –
//красный
else pix[k++]
= 0; // Правый верхний угол —
//прозрачный
// Создается
прямоугольное изображение размером (w, h),
//
заполненное массивом пикселов pix, с длиной строки w
Image
im = createImage(new MemoryImageSource(w, h, pix, 0, w));
Cursor
curs = tk.createCustomCursor(im, p, null);
someComp.setCursor(curs);
В этом
примере создается курсор в виде красного прямоугольного треугольника с катетами
размером 32 пиксела и устанавливается в каком-то компоненте someComp.
События
Событие ComponentEvent
происходит при перемещении компонента, изменении его размера, удалении с экрана
и появлении на экране.
Событие
FocusEvent возникает при получении или потере фокуса.
Событие
KeyEvent проявляется при каждом нажатии и отпускании клавиши, если компонент
имеет фокус ввода.
Событие
MouseEvent происходит при манипуляциях мыши на компоненте.
Приложение
3. Обработка действий мыши и клавиатуры
Обработка действий мыши
Событие
MouseEvent возникает в компоненте по любой из семи причин:
·
нажатие кнопки
мыши — идентификатор MOUSE_PRESSED;
·
отпускание кнопки
мыши — идентификатор MOUSE_RELEASED;
·
щелчок кнопкой
мыши — идентификатор MOUSE_CLICKED (нажатие и отпускание не различаются);
·
перемещение мыши
идентификатор MOUSE_MOVED;
·
перемещение мыши
с нажатой кнопкой — идентификатор MOUSE_DRAGGED;
·
появление курсора
мыши в компоненте — идентификатор MOUSE_ENTERED;
·
выход курсора
мыши из компонента — идентификатор MOUSE_EXITED.
Для их
обработки есть семь методов в двух интерфейсах:
public
interface MouseListener extends EventListener{
public
void mouseClicked(MouseEvent e);
public
void mousePressed(MouseEvent e) ;
public
void mouseReleased(MouseEvent e);
public
void mouseEntered(MouseEvent e);
public
void mouseExited(MouseEvent e);
}
public
interface MouseMotionListener extends EventListener{
public
void mouseDragged(MouseEvent e);
public
void mouseMoved(MouseEvent e);
}
Эти методы
могут получить от аргумента е координаты курсора мыши в системе координат
компонента методами e.getx(), e.getv(), или одним методом e.getPoint(),
возвращающим экземпляр класса Point.
Двойной
щелчок кнопкой мыши можно отследить методом e.getClickCount(),
возвращающим количество щелчков. При перемещении мыши возвращается 0.
Узнать, какая
кнопка была нажата, можно с помощью метода e.getModifiers() класса inputEvent
сравнением со следующими статическими константами класса inputEvent:
·
BUTTON1_MASK — нажата первая кнопка, обычно
левая;
·
BUTTON2_MASK — нажата вторая кнопка, обычно
средняя, или одновременно нажаты обе кнопки на двухкнопочной мыши;
·
BUTTON3_MASK — нажата третья кнопка, обычно
правая.
Событие
KeyEvent происходит в компоненте по любой из трех причин:
·
нажата клавиша
идентификатор KEY_PRESSED;
·
отпущена клавиша
идентификатор KEY_RELEASED;
·
введен символ
идентификатор KEY_TYPED.
Последнее
событие возникает из-за того, что некоторые символы вводятся нажатием
нескольких клавиш, например, заглавные буквы вводятся комбинацией клавиш
<Shift>+<буква>. Вспомните еще <Аlt>-ввод в MS Windows.
Нажатие функциональных клавиш, например <F1>, не вызывает событие
KEY_TYPED.
Обрабатываются
эти события тремя методами, описанными в интерфейсе:
public
interface KeyListener extends EventListener{
public
void keyTyped(KeyEvent e);
public
void keyPressed(KeyEvent e);
public
void keyReleased(KeyEvent e);
}
Аргумент е
этих методов может дать следующие сведения.
Метод
e.getKeyChar() возвращает символ Unicode типа char, связанный с клавишей. Если
с клавишей не связан никакой символ, то возвращается константа CHAR_UNDEFINED.
Метод
e.getKeyCode () возвращает код клавиши в виде целого числа типа int. В классе
KeyEvent определены коды всех клавиш в виде констант, называемых виртуальными
кодами клавиш (virtual key codes), например, VK_FI, VK_SHIFT, VK_A, VK_B,
VK_PLUS. Они перечислены в документации к классу KeyEvent. Фактическое значение
виртуального кода зависит от языка и раскладки клавиатуры. Чтобы узнать, какая
клавиша была нажата, надо сравнить результат выполнения метода getKeyCode() с
этими константами. Если кода клавиши нет, как происходит при наступлении
события KEY_TYPED, то возвращается значение VK_UNDEFINED.
Чтобы узнать,
не нажата ли одна или несколько клавиш-модификаторов <Alt>, <Ctrl>,
<Meta>, <Shift>, надо воспользоваться унаследованным от класса
inputEvent методом getModifiers() и сравнить его результат с константами
ALT_MASK, CTRL_MASK, META_MASK, SHIFT_MASK. Другой способ — применить логические
методы isAltDown(), isControlDown(), isMetaDown(), isShiftDown().
Приложение 4. Нити процессов
Работу
многозадачной системы можно упростить и ускорить, если разрешить
взаимодействующим процессам работать в одном адресном пространстве. Такие
процессы называются threads. В русской литературе предлагаются различные
переводы этого слова. Буквальный перевод — "нить". Часто переводят
thread как "поток" или "подпроцесс".
Создание
потоков и управление ими — это дело операционной системы, но в язык Java
введены средства для выполнения этих действий. Поскольку программы, написанные
на Java, должны работать во всех операционных системах, эти средства позволяют
выполнять только самые общие действия.
Когда
операционная система запускает виртуальную машину Java для выполнения
приложения, она создает один процесс с несколькими потоками. Главный (main)
поток выполняет байт-коды программы, а именно, он сразу же обращается к методу
main() приложения. Этот поток может породить новые потоки, которые, в свою
очередь, способны породить потоки и т. д. Главным потоком аплета является один
из потоков браузера, в котором аплет выполняется. Главный поток не играет
никакой особой роли, просто он создается первым.
Поток в Java
создается и управляется методами класса Thread. После создания объекта этого
класса одним из его конструкторов новый поток запускается методом start().
Получить
ссылку на текущий поток можно статическим методом
Thread.currentThread();
Класс Thread
реализует интерфейс Runnable. Этот интерфейс описывает только один метод run().
Новый поток будет выполнять то, что записано в этом методе. Впрочем, класс
Thread содержит только пустую реализацию метода run(), поэтому класс Thread не
используется сам по себе, он всегда расширяется. При его расширении метод run()
переопределяется.
Метод run()
не содержит аргументов, т. к. некому передавать их значения в метод. Он не
возвращает значения, его некуда передавать. К методу run() нельзя обратиться из
программы, это всегда делается автоматически исполняющей системой Java при
запуске нового потока методом start ().
Итак, задать
действия создаваемого потока можно двумя способами: расширить класс Thread или
реализовать интерфейс Runnable. Первый способ позволяет использовать методы
класса Thread для управления потоком. Второй способ применяется в тех случаях,
когда надо только реализовать метод run(), или класс, создающий поток, уже
расширяет какой-то другой класс.
Посмотрим,
какие конструкторы и методы содержит класс Thread.
Класс
Thread и интерфейс Runnable.
В классе
Thread семь конструкторов:
·
Thread(ThreadGroup
group, Runnable target, String name) — создает подпроцесс с именем name, принадлежащий группе group и
выполняющий метод run() объекта target.
Это основной конструктор, все остальные обращаются к нему с тем или иным
параметром, равным null;
·
Thread() — создаваемый поток будет выполнять
свой метод run();
·
Thread(Runnable
target);
·
Thread(Runnable
target, String name);
·
Thread(String
name);
·
Thread(ThreadGroup
group, Runnable target);
·
Thread(ThreadGroup
group, String name).
Имя потока name не имеет никакого значения, оно не используется,
виртуальной машиной Java и применяется только для различения потоков в
программе.
После
создания потока его надо запустить методом start().
Виртуальная машина Java начнет выполнять метод run() этого объекта-потока.
Поток
завершит работу после выполнения метода run(). Для уничтожения объекта-потока
вслед за этим он должен присвоить значение null.
Выполняющийся
поток можно приостановить статическим методом sleep(long ms) на ms миллисекунд.
Если вычислительная система может отсчитывать наносекунды, то можно
приостановить поток с точностью до наносекунд методом sleep(long ms, int
nanosec).
В примере 4
класс TwoThreads реализует интерфейс Runnable. Здесь нельзя использовать методы
класса Thread, но зато класс TwoThreads3 может быть расширением другого класса.
Например, можно сделать его аплетом, расширив класс Applet или JApplet.
Пример 4. Реализация интерфейса Runnable
class
TwoThreads3 implements Runnable{
private
String msg;
TwoThreads3(String
s){ msg = s; }
public
void run(){
for(int
i = 0; i < 20; i++){
try{
Thread.sleep(100);
}
catch(InterruptedException
ie){}
System.out.print(msg
+ " ");
}
System.out.println("End
of thread.");
}
public
static void main (String[] args){
new
Thread(new TwoThreads3("PING"), "Thread 1").start ();
new
Thread(new TwoThreads3("pong"), "Thread 2").start ();
System.out.println();
}
}
Чаще всего в
новом потоке задаются бесконечные действия, выполняющиеся на фоне основных
действий: проигрывается музыка, на экране вращается анимированный логотип
фирмы, бежит строка. Для реализации такого потока в методе run() задается
бесконечный цикл, останавливаемый после того, как объект-поток получит значение
null.
В примере 5
показан другой вариант той же самой программы, в которой метод run()
выполняется до тех пор, пока текущий объект-поток th совпадает с объектом go, запустившим текущий поток. Для прекращения его выполнения
предусмотрен метод stop(), к которому обращается главный поток. Это стандартная
конструкция, рекомендуемая документацией J2SDK. Главный поток в данном примере
только создает объекты-потоки, ждет одну секунду и останавливает их.
Пример 5. Прекращение работы
потоков
class
TwoThreads implements Runnable{
private
String msg;
private
Thread go;
TwoThreads(String
s){
msg
= s;
go
= new Thread(this);
go.start();
}
public
void run(){
Thread
th = Thread.currentThread();
while(go
== th){
try{
Thread.sleep(100);
}
catch(InterruptedException
ie){}
System.out.print(msg
+ " ");
}
System.out.println("End
of thread.");
}
public
void stop(){go = null;}
public
static void main(String[] args){
TwoThreads
thl = new TwoThreads ("PING");
TwoThreads
th2 = new TwoThreads ("pong");
try{
Thread.sleep(1000);
}
catch(InterruptedException
ie){}
thl.stop();
th2.stop();
System.out.printlnf);
}
}
Приложение
5. Согласование
работы нескольких потоков
Возможность
создания многопоточных программ заложена в язык Java с самого его создания. В
каждом объекте есть три метода wait() и один метод notify(), позволяющие
приостановить работу потока с этим объектом, позволить другому потоку
поработать с объектом, а затем уведомить (notify) первый поток о возможности
продолжения работы. Эти методы определены прямо в классе Object и наследуются всеми классами.
С каждым
объектом связано множество потоков, ожидающих доступа к объекту (wait set).
Вначале этот "зал ожидания" пуст.
Основной
метод wait(long millisec) приостанавливает текущий поток this, работающий с объектом, на millisec
миллисекунд и переводит его в "зал ожидания", в множество ожидающих
потоков. Обращение к этому методу допускается только в синхронизированном блоке
или методе, чтобы быть уверенными в том, что с объектом работает только один
поток. По истечении millisec или после того, как объект получит уведомление
методом notify(), поток готов возобновить работу. Если аргумент millisec равен
0, то время ожидания не определено и возобновление работы потока возможно
только после того, как объект получит уведомление методом notify().
Отличие
данного метода от метода sleep() в том, что метод wait() снимает блокировку с
объекта. С объектом может работать один из потоков из "зала
ожидания", обычно тот, который ждал дольше всех, хотя это не гарантируется
спецификацией JLS.
Второй метод
wait() эквивалентен wait(0). Третий метод wait (long millisec, int nanosec)
уточняет задержку на nanosec наносекунд, если их сумеет отсчитать операционная
система.
Метод
notify() выводит из "зала ожидания" только один, произвольно
выбранный поток. Метод notifyAll() выводит из состояния ожидания все потоки.
Эти методы тоже должны выполняться в синхронизированном блоке или методе.
Приложение 6. Пример программы «Бегущая строка»
Приведем
пример аплета, реализующего прокручивающуюся строку. В отличие от тега языка
Microsoft DHTML marquee, Java программа может быть легко модифицирована в
зависимости от требований Web дизайнера. Параметры в этот аплет можно
передавать из языка HTML.
// Файл
AppletThread.java — Аплет, прокручивающий информационную строку
import
java.awt.*;
// Основной
класс аплета, реализующий нить процесса
public
class AppletThread extends java.applet.Applet implements Runnable{
String s; //
прокручивающаяся строка
Thread
thread; // ссылка на нить процесса аплета
public void
init(){
// Создаем и
стартуем нить процесса текущего аплета
thread
= new Thread(this);
thread.start();
// Получаем
из HTML страницы строку для прокрутки
s
= getParameter("Marquee");
}
public
void run(){
// Бесконечно
прокручиваем полученную строку
for (;;){
char c; //
вспомогательная переменная
try{
//
Принудительно вызываем метод paint
repaint();
// Ждем 0.3
секунды
Thread.sleep(300);
// Алгоритм
для прокрутки строки:
//1. Выбираем
первый символ текущей строки
c =
s.charAt(0);
//2. Смещаем
текущую строку на один символ вперед
s
= s.substring(1, s.length());
//3. В конец
смещенной строки записываем из буферной переменной первый символ
s
+= c;
}
catch
(InterruptedException e){}
}//конец
цикла for (;;)
}//конец
run()
public void
destroy(){
// При
завершении аплета “обнуляем” нить процесса
if
(thread != null){thread = null;}
}
public
void paint(Graphics g){
// Рисуем полученную
строку в графическом контексте аплета
g.drawString(s,
10, 20);
}
}
Создадим
соответствующий HTML файл.
<!-- Файл
AppletThread.html >
<!-- Аплет,
прокручивающий в броузере информационную строку >
<applet
code=AppletThread width=100 height=40>
<param
name="Marquee" value="Hello, Thread!">
</applet>
Обратите
внимание на следующее важное обстоятельство. Мы не можем обратиться прямо к
методу paint() для перерисовки окна компонента, потому что выполнение этого
метода связано с операционной системой — метод paint() выполняется
автоматически при каждом изменении содержимого окна, его перемещении и
изменении размеров. Для запроса на перерисовку окна в классе Component есть метод repaint().
Метод
repaint() ждет, когда представится возможность перерисовать окно, и потом
обращается к методу update(Graphics g). При этом нескольку обращений к
repaint() могут быть произведены исполняющей системой Java за один раз.
|