Принципы построения графического интерфейса

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

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

За десятилетия развития вычислительной техники создано много различных графических систем: MS Windows, X Window System, Macintosh. В каждой из них свои правила построения окон и их компонентов: меню, полей ввода, кнопок, списков, полос прокрутки. Эти правила сложны и запутаны. Графические API, предназначенные для создания пользовательского интерфейса, содержат сотни функций.

Для облегчения создания окон и их компонентов написаны библиотеки функций и классов: MFC, Motif, OpenLook, Qt, Tk, Xview, OpenWindows, OpenGL, GTK+ и множество других. Каждый класс такой библиотеки описывает сразу целый графический компонент, управляемый методами этого и других классов.

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

В первой версии JDK задачу решили следующим образом: были разработаны интерфейсы, содержащие методы работы с графическими объектами. Классы графической библиотеки Java реализуют эти интерфейсы для создания приложений. Приложения Java используют методы этих интерфейсов для размещения и перемещения графических объектов, изменения их размеров, взаимодействия объектов друг с другом.

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

Такие интерфейсы были названы peer-интерфейсами.

Библиотека классов Java, основанных на peer-интерфейсах, получила название AWT (Abstract Window Toolkit). Для вывода на экран объекта, созданного в приложении Java и основанного на peer-интерфейсе, создается парный ему (peer-to-peer) объект графической подсистемы операционной системы, который и отображается на экране. Поэтому графические объекты AWT в каждой графической среде имеют вид, характерный для этой среды: в MS Windows, Motif, OpenLook, OpenWindows, — везде окна, созданные в AWT, выглядят как "родные" окна этой графической среды.

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

Именно из-за такой реализации peer-интерфейсов и других "родных" (native) методов, написанных главным образом на языке С++, приходится для каждой платформы выпускать свой вариант JDK.

В версии JDK 1.1 библиотека AWT была переработана. В нее добавлена возможность создания компонентов, полностью написанных на Java и не зависящих от peer-интерфейсов. Такие компоненты стали называть "легкими" (lightweight), в отличие от компонентов, реализованных через peer-интерфейсы, названных "тяжелыми" (heavy).

"Легкие" компоненты везде выглядят одинаково, сохраняют заданный при их создании вид (look and feel). Более того, приложение можно разработать таким образом, чтобы после его запуска можно было выбрать какой-то определенный вид: "Motif1, "Metal", "Windows 95" или еще какой-нибудь другой, и сменить этот вид в любой момент работы.

Эта интересная особенность "легких" компонентов получила название PL&F (Pluggable Look and Feel). Это сокращение иногда записывают в виде "plaf'.

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

Библиотека классов Swing поставлялась как дополнение к JDK 1.1. В следующие версии Java SE JDK она включена наряду с AWT как основная графическая библиотека классов, реализующая идею "100 % Pure Java".

В Java SE библиотека AWT значительно расширена не только библиотекой Swing, но и добавлением новых средств рисования, вывода текстов и изображений, получивших название Java 2D, и средств, реализующих перемещение текста методом DnD (Drag and Drop).

Кроме того, в Java SE включены новые методы ввода/вывода Input Method Framework и средства связи с дополнительными устройствами ввода/вывода, такими как световое перо или клавиатура Брайля, названные Accessibility.

Все перечисленные средства Java SE: AWT, Swing, Java 2D, DnD, Input Method Framework и Accessibility — составили библиотеку графических средств Java, названную JFC (Java Foundation Classes).

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

Компонент и контейнер

Основное понятие графического интерфейса пользователя (ГИП) — компонент (component) графической системы. В русском языке слово "компонент" подразумевает просто составную часть, элемент чего-нибудь, но в графическом интерфейсе это понятие гораздо конкретнее. Оно означает отдельный, полностью определенный элемент, который можно использовать в графическом интерфейсе независимо от других элементов. Например, поле ввода, кнопка, строка меню, полоса прокрутки, радиокнопка (переключатель). Само окно приложения — тоже его компонент. Компоненты могут быть и невидимыми, например панель, объединяющая компоненты, тоже является компонентом.

Вы не удивитесь, узнав, что в AWT компонентом считается объект класса Component или объект всякого класса, расширяющего класс Component. В классе Component собраны общие методы работы с любым компонентом графического интерфейса пользователя. Этот класс — центр библиотеки AWT.

Каждый компонент перед выводом на экран помещается в контейнер (container). Контейнер "знает", как поместить компоненты на экран. Разумеется, в языке Java контейнер — это объект класса Container или всякого его расширения. Прямой наследник этого класса — класс JComponent — вершина иерархии многих компонентов библиотеки Swing.

Создав компонент — объект класса Component или его расширения, следует добавить его к предварительно созданному объекту класса Container или его расширения одним из методов контейнера add ().

Класс Container сам является невидимым компонентом, он расширяет класс Component. Таким образом, в контейнер наряду с компонентами можно помещать контейнеры, в которых находятся какие-то другие компоненты, достигая тем самым большой гибкости расположения компонентов.

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

? строка заголовка (title bar), с левой стороны которой необходимо поместить кнопку контекстного меню, а с правой — кнопки сворачивания и разворачивания окна и кнопку закрытия приложения;

? окно должно быть окружено рамкой (border), реагирующей на действия мыши.

Окно с этими компонентами в готовом виде описано в классе Frame. Чтобы создать окно в библиотеке AWT, достаточно сделать свой класс расширением класса Frame, как показано в листинге 8.1. Всего восемь строк текста, и окно готово.

Листинг 8.1. Слишком простое окно приложения AWT

import java.awt.*;

class TooSimpleFrame extends Frame{

public static void main(String[] args){

Frame fr = new TooSimpleFrame(); fr.setSize(400, 150); fr.setVisible(true);

}

}

Класс TooSimpleFrame обладает всеми свойствами класса Frame, являясь его расширением. В нем создается экземпляр окна fr, и методом retsiref) устанавливаются размеры окна на экране — 400x150 пикселов. Если не задать размер окна, то на экране появится окно минимального размера — будет видна только строка заголовка. Конечно, потом окно можно растянуть с помощью мыши до любого размера.

Затем окно выводится на экран методом setVisible(true). Дело в том, что, с точки зрения библиотеки AWT, создать окно — значит, выделить область оперативной памяти, заполненную нужными пикселами, а вывести содержимое этой области на экран — уже другая задача, которую и решает метод setVisible (true ).

Конечно, такое окно непригодно для работы. Не говоря уже о том, что у него нет заголовка, окно нельзя закрыть. Хотя его можно перемещать по экрану, менять размеры, сворачивать на панель задач и раскрывать, но команду завершения приложения мы не запрограммировали. Окно нельзя закрыть ни щелчком кнопки мыши на кнопке с крестиком в правом верхнем углу окна, ни комбинацией клавиш <Alt>+<F4>. Приходится завершать работу приложения средствами операционной системы, например комбинацией клавиш <Ctrl>+<C>.

В листинге 8.2 к программе листинга 8.1 добавлены заголовок окна и обращение к методу, позволяющему завершить приложение.

Листинг 8.2. Простое окно приложения

import java.awt.*;

import java.awt.event.*;

class SimpleFrame extends Frame{

SimpleFrame(String s){ super(s);

setSize(400, 150); setVisible(true);

addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0);

}

});

}

public static void main(String[] args){ new SimpleFrame(" Моя программа");

}

}

Для того чтобы показать разные варианты построения программы, в нее добавлен конструктор класса SimpleFrame, обращающийся к конструктору своего суперкласса Frame, который записывает свой аргумент s в строку заголовка окна.

В конструктор перенесена установка размеров окна, вывод его на экран и добавлено обращение к методу addWindowListener (), реагирующему на действия с окном. В качестве аргумента этому методу передается экземпляр безымянного внутреннего класса, расширяющего класс WindowAdapter. Этот безымянный класс реализует метод windowClosing (), обрабатывающий попытку закрытия окна. Данная реализация очень проста — приложение завершается статическим методом exit () класса System. Окно при этом закрывается автоматически.

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

Итак, окно готово. Но оно пока пусто. Выведем в него, по традиции, приветствие "Hello, XXI Century World!", правда, слегка измененное. В листинге 8.3 представлена полная программа этого вывода, а рис. 8.1 демонстрирует окно.

Листинг 8.3. Графическая программа с приветствием

import java.awt.*;

import java.awt.event.*;

class HelloWorldFrame extends Frame{

HelloWorldFrame(String s){ super(s);

}

public void paint(Graphics g){

g.setFont(new Font("Serif", Font.ITALIC|Font.BOLD, 30)); g.drawString("Hello, XXI Century World!", 20, 100);

}

public static void main(String[] args){

Frame f = new НеИоИогШЕгатеСЗдравствуй, мир XXI века!"); f. setSize(400, 150); f.setVisible(true);

f.addWindowListener(new WindowAdapter(){

public void windowClosing(WindowEvent ev){

System.exit(0);

}

});

}

}

Рис. 8.1. Окно программы-приветствия

Для вывода текста мы переопределяем метод paint () класса Component. Класс Frame всегда наследует этот метод, но с пустой реализацией.

Метод paint () получает в качестве аргумента экземпляр g класса Graphics, умеющего, в частности, выводить на экран текст методом drawString (). В этом методе кроме текста мы указываем положение начала строки в окне — 20 пикселов от левого края и 100 пикселов сверху. Эта точка — левая нижняя точка первой буквы текста, H.

Кроме того, мы установили новый шрифт "Serif" большего размера — 30 пунктов, полужирный, курсив. Всякий шрифт — объект класса Font, а задается он методом

setFont ( ) класса Graphics.

Работу со шрифтами мы рассмотрим в следующей главе.

В листинге 8.3, для разнообразия, мы вынесли вызовы методов установки размеров окна, вывода его на экран и завершения программы в метод main ().

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

Иерархия классов AWT

На рис. 8.2 показана иерархия основных классов AWT. Основу ее составляют готовые компоненты: Button, Canvas, Checkbox, Choice, Container, Label, List, Scrollbar, TextArea, TextField, MenuBar, Menu, PopupMenu, MenuItem, CheckboxMenuItem. Если этого набора не хватает, то от класса Canvas можно породить собственные "тяжелые" компоненты, а от класса Component — "легкие" компоненты.

Object

-Component — -Color -Cursor -Font -FontMetrics - Image -Polygon -BorderLayout -Card Layout -FlowLayout -GridBagLayout -GridLayout

-Button —Canvas -Checkbox — Choice —Container —r- JComponent

Label — List Scrollbar —T extComponent

t TextArea TextField

Panel-Applet

— ScrollPane

- JApplet

Window -p Dialog —p FileDialog kjWindow L JDialog L Frame — JFrame

GridBagConstaints

-Graphics -Graphics2D

Point2D-Point

—RectangularShape — Rectangle2D — Rectangle CheckboxGroup -MenuShortcut

MenuComponent-p MenuItem —Menu-PopupMenu

-Event L MenuBar I— CheckboxMenuItem

-EventObject —AWTEvent MediaTracker

Рис. 8.2. Иерархия основных классов AWT

Основные контейнеры — это классы Panel, ScrollPane, Window, Frame, Dialog, FileDialog. Свои "тяжелые" контейнеры можно породить от класса Panel, а "легкие" — от класса

Container.

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

На рис. 8.2 показаны и начальные классы иерархии библиотеки Swing — классы

JComponent, JWindow, JFrame, JDialog, JApplet.

Окно библиотеки Swing

Для получения окна с помощью средств библиотеки Swing необходимо импортировать в свою программу пакет javax.swing и расширить класс JFrame, как показано в листинге 8.4. Вместо длинного метода закрытия окна можно обратиться к методу

setDefaultCloseOperation(JFrame.EXIT ON CLOSE);

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

WindowConstants, предписывают:

? dispose_on_close — закрыть окно и освободить память, занимаемую им, но не завершать приложение;

? do_nothing_on_close — игнорировать команду закрытия окна;

? hide_on_close — только убрать окно с экрана. Это значение по умолчанию.

Фон окна Swing серый, поэтому в конструктор добавлен еще метод setBackground(Color.WHITE), устанавливающий белый цвет фона.

Листинг 8.4. Простое окно приложения Swing

import java.awt.*;

import javax.swing.*;

class SimpleFrame extends JFrame{

SimpleFrame(String s){ super(s);

setBackground(Color.WHITE); setSize(400, 150); setVisible(true);

setDefaultCloseOperation(EXIT ON CLOSE);

}

public void paint(Graphics g){

g.setFont(new Font("Serif", Font.ITALIC|Font.BOLD, 30)); g.drawString("Hello, XXI Century World!", 20, 100);

}

public static void main(String[] args){ new SimpleFrame(" Моя программа");

}

Метод paint () принадлежит классу Component, он выглядит точно так же, как в листинге 8.3. Его можно без всяких изменений перенести в программу, использующую библиотеку Swing.

Использование системных приложений

В большинстве операционных систем пользователь устанавливает для себя браузер по умолчанию, который открывается, когда пользователь выбирает файл с расширением html. Файлы с другими расширениями тоже часто связываются с приложениями, обрабатывающими их. Например, файл с расширением txt часто открывается в текстовом редакторе. Кроме того, пользователь может выбрать для себя почтовый клиент по умолчанию.

Эти возможности использует класс Desktop из пакета java.awt. У него есть методы, позволяющие запустить некоторые приложения пользователя:

? browse(uri file) — открывает браузер по умолчанию, загружающий указанный файл

file;

? mail () — открывает почтовый клиент по умолчанию;

? mail (uri mailto) — открывает почтовый клиент по умолчанию, заполняя поля "To", "Cc", "Subject", "Body" значениями, взятыми из аргумента mailto;

? edit(File file) — открывает текстовый редактор, связанный с указанным файлом file, и загружает в него файл file;

? open(File file) — открывает указанный файл file;

? print(File file) — печатает указанный файл file на принтере, назначенном этому файлу.

Связь с приложениями пользователя устанавливается через операционную систему, поэтому экземпляр класса Desktop создается не конструктором, а статическим методом getDesktop (). Эту связь удается установить не во всех системах, поэтому предварительно следует сделать проверку статическим логическим методом isDesktopSupported(). Итак, создание экземпляра класса Desktop выглядит так:

Desktop d = null; if (Desktop.isDesktopSupported()) d = Desktop.getDesktop();

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

public boolean isSupported(Desktop.Action action);

Аргументом этого метода может служить одна из следующих констант вложенного перечисления Action:

? browse — у пользователя есть браузер по умолчанию;

? MAIL — у пользователя есть почтовый клиент по умолчанию;

? EDIT — можно открыть текстовый редактор, связанный с файлами;

? OPEN — можно открыть файл;

? PRINT — можно напечатать файл.

С учетом этой проверки, обращение к методам класса Desktop будет выглядеть так:

if (d.isSupported(Desktop.Action.BROWSE))

d.browse(new URI("http://www.bhv.ru"));

if (d.isSupported(Desktop.Action.MAIL)) d.mail();

if (d.isSupported(Desktop.Action.EDIT))

d.edit(new File("/home/user/j ava/src/MyDesktop.j ava");

if (d.isSupported(Desktop.Action.OPEN))

d.open(new File("/home/user/j ava/src/MyDesktop.j ava");

if (d.isSupported(Desktop.Action.PRINT))

d.print new File("/home/user/java/src/MyDesktop.java");

System Tray

Графическое приложение Java может установить ярлык в зоне уведомлений (notification area), называемой также системным лотком (system tray) или просто треем. Эта зона обычно размещается в правом нижнем углу экрана и содержит часы и ярлыки запущенных программ. Для работы с зоной уведомлений в пакет java.awt включен класс SystemTray. Его метод add(Trayicon icon) помещает ярлык icon в зону уведомлений, а метод remove (Trayicon icon) удаляет его из зоны. Каждое приложение может поместить в зону уведомлений несколько ярлыков.

Как видно из заголовков, параметр этих методов, ярлык- это объект класса TrayIcon.

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

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

TrayIcon icon = null; if (SystemTray.isSupported()){

SystemTray tray = SystemTray.getSystemTray();

Image im = Toolkit.getDefaultToolkit.getImage("myicon.gif"); icon = new TrayIcon(im); tray.add(icon);

}

Более подробно работа с зоной уведомлений показана в документации к классу SystemTray. Она будет понятна после прочтения главы 15.

Splash Screen

Очень часто при загрузке приложения на экране вначале появляется небольшое окнозаставка (splash screen) с каким-нибудь изображением, сменяемое затем главным окном приложения. Такое окно можно открыть при запуске приложения из командной строки, указав ключ -splash. Например, если файл с изображением называется name.gif, то запустить приложение можно так:

java -splash:name.gif SimpleFrame

При запуске приложения из архива, например

java -jar SimpleFrame.jar

имя файла с изображением записывается в файл MANIFEST.MF, как показано в главе 25.

Некоторые возможности управления окном-заставкой предоставляет класс SplashScreen из пакета java.awt. Это возможность менять изображение методом setImageURL(URL image) и возможность рисовать в окне, получив ссылку на объект класса Graphics2D методом createGraphics (). После заполнения окна-заставки оно выводится на экран методом update (). Все это делается по следующей схеме:

SplashScreen splash = SplashScreen.getSplashScreen(); if (splash != null){

Graphics2D g = splash.createGraphics();

g.setPaintMode();

g.drawString("Loading...", 100, 200);

// и т. д., рисуем, как написано в главе 9. g.update();

}

Заключение

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

Оно приводит к созданию все новых библиотек классов и расширению существующих. Независимыми производителями создано уже много графических библиотек Java: KL Group, JBCL, SWAT, SWT и появляются все новые и новые библиотеки. Сведения о них можно получить на сайтах, указанных во введении.

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

Вопросы для самопроверки

1. Что такое графический интерфейс пользователя?

2. Что такое графическая библиотека классов?

3. Что называется графическим компонентом?

4. Назовите известные вам графические компоненты.

5. Что такое контейнер в графическом интерфейсе?

6. Будет ли основное окно приложения контейнером?

7. Можно ли использовать библиотеку Swing без библиотеки AWT?

8. Какая разница между компонентами AWT и компонентами Swing?

9. Можно ли совсем отказаться от компонентов библиотеки AWT?

ГЛАВА 9