    Глава 3 

    Основы GUI 

    Термин GUI (Graphical User Interface — графический пользовательский 
интерфейс) обозначает интерфейсы оконного типа. На сегодняшний день наиболее 
распространенными являются приложения, созданные для операционных систем с 
"родным" оконным интерфейсом, таких как Windows-95, X-Window, Macintosh, OS/2. 
    Платформно-независимый язык Java делает возможным создание использующих 
окна программ для работы в перечисленных популярных системах, где приоритет 
отдается удобству пользователей. Одной из важнейших частей библиoтeки классов 
Java является AWT (Abstract Windows Toolkit — библиотека абстрактных 
классов работы с окнами). AWT представляет собой несколько упрощенную 
абстракцию оконных операционных систем, описывающую их общие функциональные 
возможности. В частности, в AWT содержится класс Windows, определяющий общие 
свойства окон. Когда Java-программа с GUI запущена в операционной системе, 
работающей с окнами, объекты типа Window представляются пользователю как окна 
этой операционной системы. Во всех основных операционных системах такого типа 
применяются очень похожие методы работы с окнами. И это очень удобно, 
поскольку дает возможность графическим пользовательским интерфейсам Java 
успешно работать даже в незнакомых операционных системах. 

    77 


    Пример GUI: HelloX 

    Программа HelloX не является Java-программой с управлением типа ActiveX, 
хотя это и подразумевается в ее названии. Однако с использованием Visual J++ 
эту программу очень легко преобразовать в программу с управлением типа 
ActiveX, как это будет показано в главе 7. Тем, кто знаком с приложением 
HelloWorld по главе 1 настоящего издания или по надоевшим примерам из 
учебников по программированию, цель этой программы известна: отображение 
строки "Hello, <X>", где X — некоторая введенная пользователем строка. 
Основными элементами имеющегося в программе графического пользовательского 
интерфейса являются окна верхнего уровня (типа контейнера), менеджеры 
компоновки и компоненты. Текст программы HelloX приведен ниже: 

    Пример 3.1. Программа HelloX 

import   java.awt.*; 

// HelloX: Класс программы. В нем содержится точка входа в 
// программу. Каждый экземпляр создает GUI HelloX. 
public class HelloX { 
	// Статический метод main: точка входа в программу, 
	public static void main( String[] astrArgs) { 
		if(1 == astrArgs.length) {
		    HelloX gui = new HelloX(astrArgs[0]);   
		} else {
		    System.err.println("Usage: Java HelloX <display-string>"); 
		}
	} 
	// Конструктор HelloX: принимает строку в качестве параметра. 
	// Конструкторы GUI создали окно верхнего уровня 
	// и HelloXComponent. 
                 public HelloX(String strDisplay) { 
		// Создание окна верхнего уровня и его менеджера компоновки. 
		Frame f = new Frame("HelloX Example Program"); 
		f.setLayout(new BorderLayout()); 
		//f.resize(300, 200); deprecated
		f.setSize(300, 200); 
		// Создание компонента string display. 
		HelloXComponent c = new HelloXComponent(strDisplay); 
		// Совместное размещение этих двух частей, 
		f.add("Center", c); 
		f.show(); //
	}
}

class HelloXComponent extends Canvas { 
	// Конструктор класса берет display string и хранит ее. 
	public HelloXComponent(String strDisplay) { 
		m_strDisplay = new String(strDisplay); 
	} 


    78	Visual J++: основы программирования 


	// Метод paint () вызывается при необходимости отображения 
	// компонента. 
	public void paint(Graphics g) { 
		String strDisplay = "Hello," + m_strDisplay + "!"; 
		FontMetrics fm = g.getFontMetrics(); 
		int cxText = (getSize().width / 2) - (fm.stringWidth(strDisplay)/2); 
		int cyText = (getSize().height / 2)- (fm.getHeight()/2); 
		// int cxText = (size().width / 2) - (fm.textWidth(strDisplay)/2); deprecated
		// int cyText = (size().height / 2) - (fm.textHeight(strDisplay)/2); deprecated
	 	g.drawString(strDisplay, cxText, cyText); 
	} 
	// Переменные, объявленные как private, 
	private String m_strDisplay = null; 
} 



    Создание приложения HelloX в Visual J++ 

    Для создания приложения HelloX в Visual J++ необходимо выполнить следующие операции: 
    1.   Запустить Visual J++. 
    2.   Используя команду New из меню File, а затем диалоговое окно Choose 
Туре, создать новое рабочее пространство проекта. Задать новый каталог в 
произвольном системном каталоге (можете попробовать msdev\Project, если не 
придумаете ничего лучшего), дать ему имя HelloX (расширение при этом не 
определяется, так как VJ++ автоматически применяет соответствующее расширение файла). 
    На экране, в левой части окна Developer Studio, должно появиться окно 
рабочего пространства с папкой классов, имеющей название HelloX. Правда, 
классов в проекте пока еще нет. Рассмотрим процесс создания класса HelloX. 
    3.   Из меню Insert выбрать опцию New Java Class. В поле ввода Name 
диалогового окна Create New Class ввести HelloX. Поля Extends и Package 
оставить незаполненными. Установить только опцию Public, показывающую, что 
класс HelloX следует объявить как public. Нажать кнопку ОК. 
    4.   В появившийся в окне редактора текст класса HelloX занести код, 
указанный в примере. 
    5.   В меню Insert выбрать опцию New Java Class. В поле диалога ввести имя 
нового класса HelloXComponent, чьим родительским классом является Canvas, в 
поле ввода Extends ввести имя Canvas. 
    Если программа набрана, выбрать опцию Rebuild All в меню Build — и 
приложение HelloX готово к запуску. 

    Компоненты 

    В приложении HelloX определен класс для  отображения строки "Hello, <Х>". 
Этот класс, имеющий название HelloXComponent, порожден от класса 

    Глава 3. Основы GUI 79 

Canvas — корневого класса пакета Java.awt. Класс Canvas порожден от класса 
Component. Иерархия перечисленных классов показана на рис. 3.1. 

    Рис. 3.1. Иерархия классов, к которым принадлежит класс HelloXComponent 

    В AWT родительским классом всех элементов GUI является класс Component. 
Component — это родительский класс класса Canvas, а также всех списков, 
кнопок, полос прокрутки и всех доступных пользователю элементов управления. В 
этом классе определены основные функциональные возможности, общие для всех 
GUI-элементов. Их можно разбить на четыре группы: 
    •	функции поддержки иерархии компонент/контейнер; 
    •	функции задания размеров и положения объектов; 
    •	функции обработки событий; 
    •	графические функции. 
    Описанию графических функций посвящена глава 5, остальные три категории 
функциональных возможностей класса Component рассмотрены в данной главе. 

    Иерархия компонент/контейнер 

    Все компоненты находятся внутри родительского контейнера. Так, кнопки ОК и 
Cancel являются компонентами, а их родительский контейнер — это диалоговое 
окно. В примере программы HelloX контейнером объекта HelloXComponent выступает 
Frame, то есть окно верхнего уровня, созданное программой. 
    Так же, как компоненты описываются классом Component, контейнеры 
описываются классом Container. На первый взгляд может показаться странным, что 
класс Container порожден от класса Component. На самом же деле в этом нет 
ничего удивительного. Что представляет собой контейнер, как не компонент, 
который может содержать (contain) другие компоненты? Все функции, которые 
выполняют компоненты, присущи и контейнеру. Его 

    80	Visual J++: основы программирования 

размеры определяются ограничивающим прямоугольником, в нем можно 
отображать другие элементы, он может обрабатывать события. 
    Каждый компонент может находиться в определенном родительском контейнере. 
Но поскольку класс Container порожден от класса Component, все контейнеры 
должны содержаться в одном контейнере, который, в свою очередь, также 
относится к определенному контейнеру, и т.д. Это соотношение 
компонент/контейнер определяет бесконечную иерархию компонентов (рис. 3.2). 
Бесконечное в иерархии компонент/контейнер является результатом аппроксимации 
конечного. В AWT применяются контейнеры верхнего уровня, то есть не имеющие 
родительских контейнеров. К таковым относится и использованный в примере 
программы HelloX контейнер Frame. 
    Чтобы создать родительский контейнер для какого-либо объекта Component, 
следует использовать метод getParent(). Позднее будет рассказано о том, как 
получить список компонентов контейнера. 

    Рис. 3.2. Иерархия компонент/контейнер простого GUI в Java 


    Размер и расположение компонентов 

    Попросту говоря, GUI-елемент, или компонент, — это прямоугольная ^область 
экрана, предназначенная для отображения чего-то, например строки, кнопки, ряда 
комплексных анимаций и т. п. Прямоугольник, определяющий положение компонента 
на экране, называется границами (bounds) компонента или ограничивающим 
прямоугольником (bounding rectangle). Протяженность границ компонента 
называется размером (size) компонента. 

    Глава 3. Основы GUI 81 

    Ограничивающий прямоугольник определяется начальной точкой, за которую 
обычно принимается левый верхний угол. Методы класса Component — bounds() и 
size() — возвращают соответственно объекты Rectangle и Dimension, которые 
определяют ограничивающий прямоугольник компонента. 
    Рассмотренная выше программа HelloX включает метод paint(), в котором, в 
свою очередь, для определения центральной точки компонента при размещении 
строки "Hello, <X>" используется метод size(). С этой целью можно использовать 
и метод bounds(). Правда, в этом случае необходимо учитывать координаты х и у 
начала прямоугольника. Сейчас же достаточно будет сказать, что метод 
location() класса Component возвращает точку, описывающую левый верхний угол 
компонента. Вместе методы size() и location() сообщают ту же информацию, что и 
прямоугольник, возвращаемый методом bounds(). 
    При помощи методов класса Component можно перемещать компоненты, изменять 
их размеры. Метод move() изменяет начальные координаты компонента, не изменяя 
его размеров. Метод resize() изменяет размеры компонента, оставляя прежними его 
начальные координаты. Учтите, что хотя вы можете задавать начальные координаты 
компонента как отрицательные величины, размещая его верхний левый угол левее и 
выше начальной точки контейнера, размеры компонента, тем не менее, всегда 
остаются положительными величинами. Метод reshape() позволяет изменять 
параметры ограничивающего прямоугольника компонента — его начальные координаты 
и размеры. 
    Однако перечисленные методы достаточно редко применяются для передвижения 
компонентов. Зачастую задача размещения компонентов в контейнере очень 
утомительна. Для ее решения создатели Java разработали менеджеры компоновки. 
Менеджер компоновки может размещать компоненты и изменять их размеры в 
соответствии с некоторыми своими критериями. Например, объект типа 
BorderLayout, используемый в примере программы HelloX, размещает компоненты 
так, чтобы они заполняли всю площадь окна Frame. Поэтому в Java нет 
необходимости часто передвигать GUI-компоненты — данную работу выполняет 
менеджер компоновки. 
    У каждого компонента имеется по два дополнительных булевых состояния, 
которые определяют его внешний вид и поведение. Компонент может быть либо 
доступен (enabled), либо недоступен (disabled). По умолчанию он считается 
доступным. У недоступных компонентов теряются интерактивные возможности. 
Например, недоступную кнопку нельзя нажать. Большинство недоступных 
компонентов окрашены в серый цвет. Для переключения кнопки из одного состояния 
в другое используются методы класса Component disabled() и enabled(). Метод 
isEnabled() сообщает, доступен компонент или нет. 
    Компонент может быть видимым (visible) или скрытым (hidden). Когда 
компонент видим, он может быть отображаемым (showing) или неотображаемым (not 
showing). Каждый компонент, который видно на экране, является видимым и 
отображаемым. Если компонент существует, но на экране его не 

    82	Visual J++: основы программирования 

видно, то он либо скрытый, либо неотображаемый. Отображается или нет 
видимый компонент — зависит от его расположения на экране. Компонент, 
размещенный слишком далеко от правой или левой, верхней или нижней границ 
родительского контейнера, является неотображаемым. Если он видим и попадает в 
границы родительского контейнера, то он отображается. Метод класса Component 
isShowing() сообщает, отображен компонент или нет (то есть находится ли он 
внутри границ контейнера и видим ли). 
    С другой стороны, скрытый компонент независимо от его размеров и 
местоположения на экране не виден. Возможность скрывать компонент бывает очень 
полезной. Предположим, например, что имеется панель инструментов, которая 
должна быть видима в том случае, когда пользователь выбирает соответствующую 
опцию. Самый простой способ сделать это — спрятать компонент, пока эта опция 
не выбрана пользователем. Методы hide() и show() класса Component позволяют 
сделать компонент скрытым или видимым. (Очень жаль, что метод show() назван 
таким, образом. Следует понять: метод show() делает компонент видимым, но 
только его помещение в родительский контейнер позволяет утверждать, что 
компонент отображен на экране.)

    Обработка событий 

    В Java имеются встроенные методы обработки событий. События (events) — это 
синхронные или асинхронные сообщения, обычно передаваемые между 
GUI-элементами в бшгьшинстве операцшхщщх систем, использующих оконный интерфейс. 
В Java все события являются экземплярами класса Event. Одна из важнейших 
функциональных возможностей всех Java-компонентов — возможность обрабатывать 
события. Примером типа события служит событие перемещения мыши. Если двигать 
мышь так, чтобы курсор перемещался внутри граничного прямоугольника 
компонента, то компонент получит несколько асинхронных сообщений о движении 
мыши. Аналогично при щелчке мышью, вводе с клавиатуры или другом виде операций 
ввода-вывода, производимым над GUI-компонентом, последний получит асинхронное 
уведомление о событии. 
    Метод handleEvent() класса Component вызывается тогда, когда компоненту 
отправляется синхронное или асинхронное уведомление о событии. В примере с 
мышью при ее перемещении по объекту типа Component система Java автоматически 
вызывает метод handleEvent() этого компонента. Этому методу передается объект 
Event. Переменныё объеткта Event точно сообщают, какой вид события произошел и 
каковы его параметры. 
    Программу HelloX можно изменить таким образом, чтобы она реагировала на 
события, поступающие от мыши. Рассмотрим пример 3.2, где приведен измененный 
класс HelloX, способный обрабатывать события перемещения курсора мыши. Всякий 
раз, когда курсор мыши появляется в ограничивающем прямоугольнике, возникает 
строка "Hello, <X>". 

    Глава 3. Основы GUI 83 


    Пример 3.2. Компонент HelloX, обрабатывающий события, которые поступают от мыши 

import   java.awt.*; 

class HelloXComp2 extends Canvas { 
	// Конструктор класса берет отображаемую строку и хранит ее. 
	public HelloXComp2 (String strDisplay)  { 
		m_strDisplay = new String(strDisplay); 
	}

	// Метод handleEvent() определяет, является ли 
	// данное событие событием передвижения мыши и, если это 
	// так, вызывает функцию обработчика. 
	public boolean handleEvent(Event evt)  { 
		if(Event.MOUSE_MOVE == evt.id) 
			return mouseMove(evt, evt.x, evt.y); 
		return false; 
	} 

	// Метод mouseMove() сохраняет координаты перемещения 
	// и заставляет компонент перерисовывать изображение, 
	public boolean mouseMove(Event evt, int x, int y) { 
		m_cxText = x; 
		m_cyText = y; 
		repaint(); 
		return true; 
	}
	
	// Метод paint() вызывается, когда необходимо отобразить 
	// сам компонент. Он дает отображение строки с последними 
	// координатами мыши, 
	public void paint(Graphics g) { 
		String strDisplay = "Hello, " + m_strDisplay + "!"; 
		g.drawstring(strDisplay, m_cxText, m_cyText); 
	} 

	// Private-переменные. // 
	private String m_strDisplay = null;
	private int m_cxText = 0; 
	private int m_cyText = 0; 
} 

    На рис. 3.3 представлен вид экрана в момент запуска новой версии программы 
HelloX. Строка текста отображается правее курсора. В классе HelloXComp2 
переопределен метод handleEvent() для выяснения вида произошедшего события и 
для вызова соответствующего метода его обработки. Этот метод называется 
обработчиком события {event handler). 

    84	Visual J++: основы программирования 


    Рис. 3.3. Окно модифицированной программы HelloX 

    Существует несколько обработчиков событий, встроенных в класс Component. 
Реализация метода handleEvent() по умолчанию действует как 
больoшой оператор switch, вызывающий соответствующий обработчик событий 
класса Component для каждого типа событий. В классе HelloXComp2 (второй версии 
класса HelloXComp) явно обрабатывались только события, связанные с перемещением 
мыши. На самом деле деле вообще необязательно переопределять метод handleEvent(), 
поскольку реализация по умолчанию всегда вызывает метод mouseMove() класса Component. 
    Все типы событий определяются значением объявленной public-переменной id 
объекта Event. В классе Event определено несколько статических целых значений, 
представляющих общие типы событий. Несколько других переменных класса Event 
предоставляют дополнительную информацию о событиях. Переменные х и у 
указывают, где именно произошло определенное событие. В частности, такие 
переменные обычно есть у событий перемещения мыши. Для указания клавиши, 
связанной с событием, предназначена переменная key. 
    Переменная  key используется в событиях, связанных с нажатием и 
отпусканием клавиш. Кроме того, при помощи переменной modifiers, указывается, 
нажимались ли клавиши [Shift], [Ctrl], [Esc] (называемая в Java МЕТА) и [Alt]. 
Чтобы определить факт нажатия специальной клавиши, необходимо осуществить 
операцию побитового ИЛИ между переменной modifiers и константами SHIFT, CTRL, 
ESC, ALT. 
    Переменная clickCount используется только для событий, связанных со 
щелчками мыши, и указывает количество выполненных щелчков. В случае двойного 
щелчка ее значение равно 2. Перемeнная arg используется для информационных 
целей. Она может иметь разное предназначение. Например, в событиях, связанных 
с нажатием кнопки или выбором пункта меню, 

    Глава 3. Основы GUI 85 

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

    Таблица 3.1. Общие типы событий 

    Константа класса		Описание события			Обработчик события 
    Event 

   ACTION_EVENT		Нажатие кнопки или выбор   		action(Event, arg) 
				пункта меню. 

   GOT_FOCUS		Компонент получил "фокус   		gotFocus(Event, arg) 
				ввода". Все нажатия клавиш 
				будут теперь обрабатываться 
				этим компонентом. 

   KEY_PRESS		Нажатие клавиши. Проверка		keyPress(Event, key) 
				переменной modifiers для 
				определения специальных 
				клавиш. 

   KEY_RELEASE		Клавиша отпущена. Modifiers		keyRelease(Event, key) 
				указывает специальную клавишу. 

   LIST_SELECT		Используется в списках. Со-    	Нет 
				общает контейнеру списка, 
				что выбран пункт списка. 

   LIST_DESELECT		Используется в списках. Со-    	Нет 
				общает контейнеру списка, что 
				выбор пункта списка отменен. 

   LOST_FOCUS		Компонент потерял фокус    		lostFocus(Event, arg) 
				ввода с клавиатуры. События 
				с клавиатуры больше не 
				пересылаются этому компоненту. 

   MOUSE_DOWN       	Нажатие кнопки мыши			mouse Down (Event, x, у) 

   MOUSE_DRAG 		Передвижение курсора мы-   		mouseDrag(Event, x, у) 
				ши при нажатой кнопке. 

   MOUSE_ENTER         	Курсор вошел в ограничи-    		mouseEnter(Event, x, у) 
				вающий прямоугольник компонента. 

   MOUSE_EXIT		Курсор покинул ограничи-    		mouseExit(Event, x, у) 
				вающий прямоугольник компонента. 

   MOUSE_MOVE		Передвижение   мыши   при    		mouseMove(Event, x, у) 
				отпущенной кнопке. 


    86 Visual J++: основы программирования 


    Константа класса		Описание события			Обработчик события Event 

   SCROLL_LINE_UP,	Эти    идентификаторы    ис-    	Нет     
   SCROLL_LINE_DOWN,	пользуются только с объек-
   SCROLL_PAGE_UP, 	тами полосы прокрутки. 
   SCROLL_PAGE_DOWN	Определяют тип происходя-. 
				щих событий. 


    Метод handleEvent() возвращает булево значение. Значение true свидетельствует о том, 
что событие полностью обработано методом handleEvent() или вызванными им 
процедурами. Значение false говорит о том, что событие не обработано или обработано 
частично. 
    Необработанные события передаются контейнеру компонента. Если контейнер 
не обработает событие (или если метод handleEvent() вернет значение false), то 
передаст его своему контейнеру и т. д. Это достаточно удобно, поскольку вместо 
размещения событий с целью обработки по нескольким классам типа Component 
можно выделить их в отдельный класс, например в класс типа Container, в 
котором может быть несколько кнопок. В таком случае события, генерируемые при 
нажатии кнопки, автоматически передаются в контейнер для соответствующей 
обработки. Такие методы обработки событий, как mouseMove() и keyDown(), также 
возвращают булево значение. 

    Компоненты пользовательской настройки 

    Чтобы не навевать уныние этим скучным текстом о компонентах, покажем, как 
можно создавать собственные пользовательские компоненты. Оценив затраченные на 
это усилия, вы не будете считать стандартные компоненты такими уж простыми и скучными. 
    В AWT определен один класс типа Component, называемый Canvas. Его 
экземпляры — это не реагирующие на события компоненты, которые можно 
разместить в любом контейнере. В Java такие универсальные компоненты можно 
применить для создания любого класса пользовательских компонентов. Ниже 
перечисляются действия, которые необходимо выполнить для создания класса 
пользовательских компонентов: 
    1.   Породить класс пользовательских компонентов от класса Canvas пакета java.awt. 
    2.   Добавить информацию о состоянии в виде переменных, соответствующие 
         public-методы которых позволят внешнему коду управлять объектами компонента. 
         Например, окно текущего состояния (полоса со шкалой 0-100%, которая часто 
         встречается в приложениях с оконным интерфейсом) должно включать в себя 
         переменные целого типа или типа с плавающей запятой, объявленные как private, 
         которые будут хранить значение, соответствующее текущему числу процентов Для 
         управления таким компонентом могут использоваться public-методы с именами 
         вроде setPercentage() или getPercentage(). 


    Глава 3. Основы GUI	87 


    3.   Переопределить данную по умолчанию (в классе Component) реализацию 
обработчиков событий в вашем классе, с тем чтобы соответствующим образом 
реагировать на взаимодействие пользователя с компонентом. Для того чтобы 
изобразить компонент, следует переопределить метод paint() созданного класса. 
    Давайте рассмотрим пример компонента с двумя состояниями. Таковым может 
служить класс Checkbox пакета AWT. Два состояния компонента обозначаются нулем 
и единицей. В зависимости от состояния компонент различным образом отображает 
себя. В примере 3.3 показана реализация класса TwoStateComp, в котором 
конструктору компонента передано два изображения. Одно изображение создается 
для состояния компонента, обозначенного как 0, а второе для состояния, 
обозначенного как 1. 

    Пример 3.3. Реализация класса TwoStateComp типа Component 

import   java.awt.*;

class TwoStateComp extends Canvas
{	
	/* ***************** 
	 * Конструкторы/деструкторы.	
	 ****************** */ 
	public TwoStateComp( Image imgState0, Image imgState1)  {
		m_aimgStates[0] = imgState0;
		m_aimgState1[1] = imgState1;
	}
	/* ********	
	 *Publoc-методы  экземпляра.
	******** */ 
	public void  setState(int  nNewState)    { 
		if((nNewState  <  0)  ||  (nNewState  >  1)) 
			throw(new IllegalArgumentException(s_strErrorInvalidStateValue)) 
		m_state = nNewState; 
		repaint();
	}
	public void paint(Graphics g)   { 
		// Особенности отображения на экране рассматриваются в главе 5. 
		g.drawlmage(m_aimgStates[m_state] , 0, 0, this); 
	}
	//////////         
	// Методы обработку события. 
	public boolean mojiseDown (Event evt, int x, int y)   { 
		m_fMouseDown = true; 
		repalnt(); 
		return true; 
	} 


    88	Visual J++: основы программирования 


	public boolean mouseEnter(Event evt, int x, int y)   { 
		m_fMouseInBounds = true; 
		repaint(); 
		return true; 
	} 
	public boolean mouseExit(Event evt, int x, int y)  { 
		m_Моизе_InBounds =  false;
		repaint () ;
		rreturn true;    
	}
	public boolean mouseUp (Event evt, int x, int y)   { 
		if(m_fMouseDown && m_fMouseInBounds)  { 
			Event evt2 = new Event(this, Event.ACTION_EVENT, null); 
			getParent().postEvent (evt) ; 
		}
		m_fMouseDown = false; 
		return true; 
	} 

	/* ******** 
	* Private-переменные экземпляра. 
	********** */
	private Image[]	m_aimgStates = new Image[2]; 
	private int		m_state = 0; 
	private boolean	m_fMouseDown = false; 
	private boolean	m_fMouseInBounds = false; 

	/* ******** 
	* Статические private-переменные. 
	******** */ 
	private static final String s_strErrorInvalidStateValue = 
		"Error: TwoState Comp may only have states 0 or 1."; 
    } 



    Контейнеры и менеджеры компоновки 

    Как уже говорилось, у всех компонентов (кроме окон верхнего уровня) 
имеется родительский контейнер. Родительским классом для всех контейнеров 
является Container. Этот класс порожден от класса Component, поскольку 
контейнер является разновидностью компонента, который может содержать 
(contain) другой компонент. Дополнительные методы класса Component делятся на 
две категории: 
    •    методы управления порожденными компонентами; 
    •    методы компоновки порожденных компонентов. 

    Глава 3. Основы GUI 89 

    Для создания компонента объектом типа Container используется метод add() 
класса Container: 

    myContainer.add(myComponent); 

    В результате компонент удаляется из текущего контейнера (если таковой 
имеется) и помещается в виде порожденного компонента в указанный контейнер. На 
дисплее может появиться лишь компонент, содержащийся в контейнере. Исключением 
из этого правила служат окна верхнего уровня, которые являются особыми 
контейнерами, не имеющими родительского контейнера. Следует заметить, что 
метод add() использовался в примере программы HelloX для помещения 
порожденного компонента в главное окно приложения. 
    Для удаления компонента из контейнера применяется метод remove() класса 
Container. Некоторые другие методы класса Container (перечислены в табл. 3.2) 
используются с целью управления порожденными компонентами. 

    Таблица 3.2. Дополнительные методы класса Container для управления компонентами 

    Метоп		Описание 

    removeAll	Удаляет все компоненты  контейнера. После  вызова  этого метода 
			компонентов в контейнере не остается. 
    getComponent	Определяет n-й компонент контейнера. 
    getComponents	Дает возможность получить перечень всех компонентов контейнера. 
    locate		Задав точку (х, у), возвращает ссылку на порожденный компонент, 
			который находится в этой точке в контейнере. 


    Менеджеры компоновки: размещение компонентов в контейнере 

    Интересно знать, в каком месте экрана контейнер отобразит добавленный в 
него компонент. За расположение порожденных компонентов внутри родительского 
контейнера в Java ответственен ряд классов, называемых менеджерами компоновки 
(Layout Manager). У каждого контёйнера имеется связанный с ним объект типа 
LayoutManager. Именно ему поручена основанная на некотором алгоритме работа по 
размещению порожденных компонентов на экране. 
    Существует несколько различных типов менеджеров компоновки, включенных в 
основную библиотеку классов. У каждого из них имеется свой алгоритм размещения 
порожденных компонентов. В частности, менеджер компоновки FlowLayout 
располагает компоненты в контейнере в направлении слева направо и сверху вниз, 
начиная с левого верхнего угла контейнера. 
    Контейнер, его порождённые компоненты и менеджер компоновки 
взаимодействуют посредством алгоритма, называемого проверкой достоверности 
(validation). В контейнере хранится флаг внутреннего состояния, показывающий, 
является контейнер правильным  (valid) или неправильным (invalid). 

    90 Visual J++: основы программирования 

    Неправильное состояние указывает, что компоненты в контейнере необходимо 
расположить по-иному. Обычно это состояние возникает, когда необходимо 
изменить размеры компонентов, убрать или добавить какой-нибудь из них. Чтобы 
заставить менеджер.хомпоновки контейнера переставить порожденные 
компоненты, можно воспользоваться методом validate() класса Container. Если 
контейнер помечен как правильный, метод validate() ничего не изменяет. Если же 
контейнер неправильный, то вызывается метод layoutContamer() менеджера 
компоновки контейнера. В различных менеджерах компоновки этот метод реализован 
по-разному, но все они выполняют следующие операции: 
    1.   Определяют текущий размер данного контейнера. 
    2.   Устанавливают желательный размер каждого компонента. Метод 
preferredSize() класса Component возвращает объект Dimension, описывающий 
нужную высоту и ширину компонента. 
    3.   Менеджер компоновки использует некоторый алгоритм подгонки 
порожденных компонентов к контейнеру. 
    Размер, возвращаемый методом preferredSize() класса Component, является 
всего лишь предпочтительным. Менеджер компоновки может проигнорировать его. 
minimumSize() класса Component возвращает минимальный размер, 
характеризуя наименьшие размеры компонента, при которых он еще нужен 
пользователю. Менеджер компоновки всегда старается сохранить компоненты по 
крайней мере на том уровне, который предписан методом minimumSize(). 
    Преимущество метода размещения компонентов при помощи менеджеров 
компоновки перед обычными методами среды Windows очевидно. В Windows 
пользователю приходится собственноручно размещать каждый порождённый 
компонент. Это может привести к возникновению некоторых проблем при работе с 
масштабируемыми окнами. В Visual J++ ResourceWizard используется менеджер 
компоновки, называемый DialogLayout. Данный менеджер размещает элемент 
управления в том месте родительского контейнера, в котором он был определен в 
шаблоне диалогового окна. Но что же происходит, если размеры родительского 
контейнера изменяются? Что если размеры диалогового окна заданы большими тех, 
которые может обеспечить приложение (если нынешний экран гораздо меньше того, 
на котором создавалось диалоговое окно)?  Менеджер компоновки способен легко 
решить эту проблему. Он наилучшим образом использует отведенное пространство 
при изменении размеров контейнера. 
    Метод setLayout() класса Container указывает, какой менеджер компоновки 
должен управлять интерфейсом контейнера. Ниже приведен фрагмент кода из 
приложения HelloX, в котором главное окно приложения связывается с объектом 
типа BorderLayout: 

    Frame f = new Frame("HelloX Example Program"); 
    f.setLayout(new BorderLayout()); 


    Глава З. Основы GUI	91 


    Различные менеджеры компоновки 

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

    FlowLayout 

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

    Рис. 3.4. Окно, созданное с использованием менеджера компоновки FlowLayout 


    BorderLavout 

    Менеджер компоновки BorderLayout может располагать не более пяти 
компонентов. Их обозначают как North, South, East, West и Center. Первые 
четыре компонента располагаются около четырех границ контейнера (верхней, 
нижней, левой и правой). Компонент Center размещается в центре контейнера. 
Компоненты North и South могут иметь высоту, которая указана в их желательных 
размерах, и обязаны иметь ширину, совпадающую с шириной родительского 
контейнера. Компоненты East и West имеют ширину, которая указана в их 
желательных размерах, и высоту, равную высоте контейнера минус высота 
северного и южного компонентов. Компонент Center занимает всю оставшуюся 
площадь. Задать положение добавляемых компонентов можно с помощью метода add() 
класса Container, имеющего два параметра. Так, в программе HelloX объект 
HelloXComponent при введении в GUI становится в окне приложения компонентом Center: 

    HelloXComponent с = new HelloXComponent(strDisplay); 
    f.add("Center", c); 


    92 Visual J++: основы программирования 


    GridLayout 

    Менеджер компоновки GridLayout размещает компоненты в сетке из одинаковых 
ячеек. Конструктору GridLayout необходимо указать, сколько рядов и столбцов 
надо создать менеджеру компоновки GridLayout. Например, в представленном далее 
коде для расположения шести кнопок в сетке два на три (рис. 3.5) используется 
объект типа GridLayout. Ячейки сетки заполняются в направлении слева направо и 
сверху вниз, первый компонент занимает левую верхнюю ячейку. 

    Рис. 3.5. Окно, созданное с помощью менеджера компоновки GridLayout 

    Panel p = new Panel(); 
    p.setLayout(new GridLayout(3, 2) ) ; 
    p.add(new Button("Upper Left")); 
    p.add(new Button("Upper Right")); 
    p.add(new Button ("Middle Left")); 
    p.add(new Button("Middle Right")); 
    p.addfnew Button("Lower Left")); 
    p.add(new Button("Lower Right")); 


    CardLayout 

    Менеджер компоновки CardLayout размещает компоненты один поверх другого, 
как карты в колоде. Видимым является только один компонент, подобно тому как в 
колоде видна только верхняя карта. Чтобы задать, какой компонент следует 
показать, используются методы first(), next(), last() и show() класса 
CardLayout. Когда компонент видим, он занимает все пространство контейнера. 
Далее будет приведена программа TabbedPanel, на примере которой показан 
принцип использования менеджера компоновки CardLayout при создании контейнера 
типа "окно с вкладками". 

    Глава 3. Основы GUI 93 


    GridBagLayout 

    GridBagLayout считается наиболее сложным из всех менеджеров компоновки, 
включенных в библиотеки Java. Его можно представить в виде сети из ячеек, 
которые можно объединять и растягивать. Основан данный менеджер, подобно 
менеджеру компоновки GridLayout, на модели сетки. Однако GridBagLayout 
способен объединять соседние ряды и столбцы, а также менять ширину столбцов и 
высоту строк в пределах размеров всего контейнера. Интерфейс, в котором 
используется GridBagLayout, показан на рис. 3.6. 

    Рис. З.6. Окно, созданное с помощью менеджера компоновки GridBagLayout 

    Если в контейнер, управляемый объектом типа GridBagLayout, добавляется еще 
один компонент, то для каждого из имеющихся компонентов необходимо задать 
объект GridBagConstraints. Объект данного класса определяет, как менеджер 
компоновки должен разместить компонент, и устанавливает размеры последнего. В 
классе GridBagConstraints имеется несколько public-переменных, с помощью 
которых указывается, как и где расположить компонент. Добавив компонент в 
контейнер, необходимо вызвать метод setConstraints(), с тем чтобы установить 
граничные значения его размеров. Список и описание переменных типа 
GridBagConstraints приведены ниже. 

    Anchor 

    Описывает, как разместить компонент в ячейке, если ее размер больше 
размера компонента. При этом может быть использована любая из следующих 
констант: GridBagConstraints.NORTH, GridBagConstraints.EAST, 
GridBag-Constraints.WEST, GridBagConstraints.SOUTH, 
GridBagConstraints.NORTHEAST, GridBagConstraints.NORTHWEST, 
GridBagConstraints. SOUTHEAST, GridBagConstraints. SOUTHWEST и 
GridBagConstraints.CENTER. 

    Insets 

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

    ipadx 

    Определяет, сколько свободного пространства следует добавить по оси X 
слева и справа от компонента. 

    94 Visual J++: основы программирования 


    ipady 

    Задает, сколько свободного пространства следует добавить по оси Y сверху и 
снизу от компонента. 

    gridx и gridy 

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

    gridwidth 

    Устанавливает число столбцов, которые должен занять компонент. Значение 
GridBagConstraints.REMAINDER говорит о том, что представлены все столбцы до 
правого края. Значение GridBagConstraints.RELATIVE показывает, что это все 
столбцы, кроме последнего. 

    gridheight 

    Отображает ту же информацию, что и gridwidth, только для строк. 

    weightx 

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

    weighty 

    Переменная аналогична weightx, но определяет степень растяжения по высоте. 

    DialogLayout 

    Как вы могли заметить, при использовании ResourseWizard для преобразования 
диалогового ресурса в класс Java в созданном классе Dialog для размещения 
компонентов используется менеджер компоновки DialogLayout. 
    DialogLayout — это простой координатный менеджер компоновки, который не 
включен в стандартные библиотеки Java. При его использовании необходимо 
указывать координаты (X,Y) места размещения, длину и ширину компонента. Все 
числа представлены в виде диалоговых единиц измерения (dialog units). 
Диалоговая единица — это число пикселей, равное 1/8 ширины и 1/4 высоты 
среднего по величине отображаемого символа. Эти единицы знакомы тем из вас, 
кто работал с диалоговыми окнами системы Windows, для остальных же читателей 
они будут непривычными. Цель применения диалоговых единиц измерения — точное 
измерение длины и ширины независимо от размеров экрана. Такие единицы 
являются большими для систем 

    Глава 3. Основы GUI	95 

с более высоким разрешением экрана. Значит, на экранах с высоким 
разрешением компоненты будут иметь большие размеры. Этот менеджер компоновки 
целесообразно использовать в диалоговых окнах с постоянными.размерами. Если 
размеры диалогового окна могут меняться, следует воспользоваться каким-либо 
другим менеджером компоновки. 

    Создание пользовательских менеджеров компоновки 

    Иногда для создания необходимого графического пользовательского интерфейса 
стандартным менеджерам компоновки не хватает гибкости. Тогда можно создать 
собственный менеджер компоновки. Помимо удачного решения непосредственных 
проблем у него есть еще одно достоинство: возможность многократного 
использования. Если вы однажды создадите такой менеджер для решения какой-либо 
частной проблемы, то повторно писать этот код вам уже не придется. Более того, 
он может быть задействован и другими пользователями. 
    В качестве примера рассмотрим процесс создания менеджера компоновки 
SplitterLayout, который может поместить любой контейнер в окно типа splitter. 
Компоненты в этом окне расположены в виде сетки, которая дает пользователю 
возможность в некоторых пределах регулировать размеры своих столбцов и строк. 
Чтобы создать такую сетку, объект типа SplitterLayout должен хранить данные о 
ширине и высоте отдельных строк и столбцов, а также предоставлять методы их 
изменения. Ниже приведена часть кода класса SplitterLayout. 

    Пример 3.4. Переменные состояния класса SplitterLayout 

public class SplitterLayout implements LayoutManager  { 
	public SplitterLayout( int cRows, int cCols ) ( 
		m__cRows = cRows ; 
		m_cCols = cCols; 
		m_anRowHeight = new int [m_cRows]; 
		m_anColWidth = new int[m_cCols]; 
	} 

	/* ********
	* Public-методы экземпляра. 
	********   "/ 

	public void setColWidth(int iCol, int cxWidth) { 
		m_anColWidth[iCol] = cxWidth; 
	} 
	public void setRowHeight(int iRow, int cyHeight)  {
		m_anRowHeight[iRow] = cyHeight; 
	} 


    96 Visual J++: основы программирования 


	/* ******** 
	* Private-переменные экземпляра. 
	******** * / 

	private int m_cCols = 0; 
	private int m_cRows = 0; 
	private int[ ] m_anColWidth = null; 
	private int[ ] m_anRowHeight = null; 
} 

    В интерфейсе LayoutManager определены перечисляемые далее методы, которые 
необходимо реализовать для каждого менеджера компоновки. После описания 
каждого метода дан пример его реализации при использовании менеджера 
компоновки SplitterLayout. 

    addLayoutComponent( String name, Component comp ) 

    Этот метод применяется с целью отслеживания менеджером компоновки 
информации о состоянии каждого компонента. Менеджер компоновки GridBagLayout, 
который хранит информацию о каждом из компонентов контейнера в объекте 
GridBagConstraint, содержит реализацию этого метода. Менеджер компоновки 
BorderLayout отслеживает данные о том, какой компонент является верхним, какой 
нижним и т. д. 
    Менеджер компоновки SplitterLayout не хранит информации о состоянии 
каждого компонента, поэтому реализация данного метода в этом классе будет пустой: 

    public  void addLayoutComponent(String  name,   Component  comp)    {} 


    removeLayoutComponent(Component comp) 

    Подобно методу addLayoutComponent() метод removeLayoutComponent() 
обеспечивает обработку информации о состоянии каждого компонента. Менеджер 
компоновки SplitterLayout также содержит пустую реализацию этого метода: 

    public void removeLayoutComponent(Component comp) (} 


    preferredLayoutSize(Container parent) 

    Данный метод возвращает объект типа Dimension, устанавливающий 
предпочтительные размеры родительского контейнера. Обычно предпочтительные 
размеры определяются путем проверки всех компонентов родительского контейнера 
методом preferredSize(). Менеджер компоновки должен учитывать существование 
бордюров (insets), т.е. отступов от  границ.контейнера, где компоненты располагаться 
не должны. Менеджер компоновки SplitterLayout при определении 
предпочтительных размеров контейнера основывается только на данных о ширине 
столбца и длине строки: 

    public Dimension preferredLayoutSize(Container parent) { 
	Dimension d = new Dimension(parent.insets().left + 
	parent.insets ().right, parent.insets().top + 


    Глава З. Основы GUI	97 


	    parent.insets () .bottom); 
	for(int i=0; i<m_anColWidth.length; i + + ) 
	    d.width += m_anColWidth[i]; 
	for(int j=0; j<m_anRowHeight.length; j++) 
	    d.height += m_anRowHeight[j]; 
	return d; 
   } 


    minimumLayoutSize(Container parent) 

    Аналогичен методу preferredLayoutSize(). Возвращает объект типа Dimension, 
описывающий размер контейнера. Причем он задает тот минимальный размер, 
начиная с которого менеджер компоновки, следуя своему алгоритму, не может 
разместить компоненты в контейнере. Поскольку менеджер компоновки 
SplitterLayout работает только с одним размером, реализация этого метода может 
возвращать то же значение, что и реализация метода preferredLayoutSize(): 

    public Dimension minimumLayoutSize(Container parent) { 
	    return preferredLayoutSize(parent) ; 
    } 


    layoutContainer(Container parent) 

    Этот метод вызывается как часть процесса проверки достоверности 
контейнера. При его вызове менеджер компоновки должен задать расположение и 
размеры всех порожденных компонентов указанного контейнера. Реализация этого 
метода в менеджере компоновки SplitterLayout такова: 

    public void layoutContainer(Container parent) { 
	Component[ ] aComps = parent.getComponents(); 
	 int xAccum = 0; 
	int yAccum = 0; 
    	for(int i=0, int y=0; ((i<aComps.length) && (y<m_cRows)); i++, y++)  { 
		for (int x=0; ( (KaComps . length) && (x<m_cCols) ) ; i + +, x + -t-)  { 
			aComp[i].reshape(xAccum, yAccum, m_anColWidth[x],  m_anRowHeight[y]); 
			xAccum += m_anColWidth[x]; 
		} 
		 xAccum = 0; 
		yAccum += m anRowHeight[y]; 
	}
   }


    98	Visual J++: основы программирования 


    Использование константы null в менеджере компоновки LayoutManager 

    В некоторых случаях с точностью до пикселя известны размеры компонентов, а 
также место в контейнере, где их следует разместить, поэтому менеджер 
компоновки здесь излишен. Вместо того чтобы сообщать ему, где необходимо 
размещать компонёнты, можно просто использовать методы rnove(), resize() или 
reshape() класса Component. 
    Чтобы управлять компонентами таким способом, менеджеру компоновки 
необходимо передать параметр null: 

    myContainer.setLayout(null); 

    Затем для каждого компонента, с тем чтобы разместить его в надлежащем 
месте, необходимо вызвать методы move(), resize(), reshape() : 

    myContainer.add( myCompl ); 
    myContainer.add( myComp2 ); 
    .....
    myComp1.move( 0, 0 ); 
    myComp2.resize(100, 75); 

    myComp2.reshape(80, 0, 100, 75); 
    ........

    При этом нужно помнить о следующем. Во-первых, чтобы вызовы методов move(), 
resize() или reshape() были эффективными, одним из предков контейнера должно 
быть окно верхнего уровня. Причем данное окно должно быть показано (для 
уверенности следует использовать метод show()). Нужно убедиться, что иерархия 
контейнер/порожденный компонент создана до начала передвижения компонентов и 
изменения их размеров. В противном случае компоненты окажутся не в надлежащем 
месте или просто пропадут из окна, когда оно в конце концов будет создано. 
    Во-вторых, при частом применении нулевой реализации менеджера компоновки 
использовать GUI максимально эффективно не удастся. Как уже отмечалось, эта 
методика подходит только ддя некоторьх случаев, весьма немногочисленных. 
Следует выяснить, можно ли в GUI установить иерархию окон, для каждого из 
которых будет применен менеджер компоновки, включенный в стандартную 
библиотеку Java. Такое вполне возможно. В частности, в представленном классе 
TabbedPanel (пример 3.5) имеется два вложенных окна, в каждом из которых для 
размещения компонентов используется свой менеджер компоновки. 
    Даже если невозможно придумать способ использования стандартного менеджера 
компоновки, для этой цели можно создать собственный менеджер. В частности, 
простого способа использования стандартного менеджера 

    Глава 3. Основы GUI 99 

компоновки не существует при реализации простого вертикального списка, 
т.е. когда каждый элемент списка является компонентом. (Если кто-нибудь из 
читателей придумает такой способ, значит, он более находчив, чем автор.) 
Вместо индивидуального размещения каждого компонента в вертикальном списке 
можно создать класс VerticalLayout. Реализация класса VerticalLayout должна 
быть очень похожа на реализацию класса FlowLayout, в которой оси X и Y 
поменялись местами. 

    Пример окна с вкладками 

    Визуально одними из наиболее оригинальных являются так называемые окна с 
вкладками (tabbed dialog), которые широко применяются в системах Windows 95 и 
Windows NT 4.x. Их особенность заключается в том, что вместо нескольких 
диалоговых окон используется одно, но с несколькими "вкладками". Это позволяет 
экономить экранное пространство при необходимости отобразить большое 
количество информации. К сожалению  в библиотеки классов Java окна с 
вкладками не включены. 
    В этом параграфе рассмотрено создание класса TabbedPanel, который 
реализует диалоговое окно с вкладками. Класс TabbedPanel можно будет 
использовать и в последующих примерах. Чтобы добавить новую вкладку, 
необходимо передать строку с ее именем методу addTab() класса TabbedPanel 
вместе с компонентами, которые будут показаны при выборе этой вкладки 
пользователем. На рис. 3.7 представлено окно приложения, использующего класс 
TabbedPanel. В классе TabbedPanel также имеются методы removeTab(), 
selectTab(), getSelectedTabName() и getSelectedTabPanel(). 


    Рис. 3. 7. Окно с вкладками 


    100 Visual J++: основы программирования 

    Прежде чем рассматривать код TabbedPanel, проведем обзор класса 
TabbedPanel. Это окно с двумя порожденными компонентами, которые располагаются 
с помощью менеджера компоновки BorderLayout. Одним из компонентов является 
окно, содержащее сами вкладки. Это северный компонент. Вторым компонентом 
является окно, в котором будут отображаться окна, соответствующие вкладкам. 
Это центральный компонент. Северный компонент располагается в верхней части 
родительского контейнера, следовательно, ряд (или ряды) вкладок будет размещен 
в верхней части окна. Оставшееся пространство окна TabbedPanel занимает 
центральный компонент. При этом все окна, отмеченные вкладками, размещаются 
друг над другом. 
    В реализации класса TabbedPanel для представления вкладок используются 
объекты типа TwoStateComp. Имея два состояния, эти компоненты управляются 
таким образом, что в каждый момент времени только один из них находится в 
состоянии "1", указывая на выбранную вкладку. Ниже показан код класса 
TabbedPanel с комментариями: 

    Пример 3.5. Класс TabbedPanel 

    import   java.awt.*; 
    import j ava . util.Hashtable;
    import Java.util.Enumeration; 
    public class TabbedPanel extends Panel 
    { 
	 / *  ******** 
	 * Конструкторы/деструкторы. 
	 ******** */
	public TabbedPanel()  { 
		 // Создание панели с вкладками менеджером компоновки 
		 // flow-layout. 
		 Panel m_panelTabs= new Panel ();. 
		 m_panelTabs.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); 

		 // Создание панели card-layout для отображения панелей 
		 // компонентов. 
		 Panel m_panelPanels = new Panel (); 
  		m_CardLayoutForPanels = new CardLayout(0, 0); 
		m_panelPanels.setLayout(m_CardLayoutForPanels); 

		// Добавление этих панелей в TabbedPanel. 
		setLayout(new BorderLayout ()); 
		add("North", m_panelTabs); 
		add("Center", m panelPanels) ; 
	}

	/* ***********
	* Public-методы экземпляра. 
	************* */


    Глава З. Основы GUI 1O1 


	public boolean addTab( String strName, Image imgSelected,  
                       Image imgUnselected, Component compPanel )  { 
		String strNameCopy = new String( strName ); 
		// Проверка того, что имя метки не используется, 
		if(m_hashNameToTab.contains( strNameCopy )) 
			return false; 
		// Создание объекта TwoStateComp для использования
		//в качестве вкладки. 
		TwoStateComp tsc = new TwoStateComp( imgSelected, imgUnselected) , 
		m_hashNameToTab.put( strNameCopy, tsc ) ; 
		m_hashNameToCompPanel.put( strName, compPanel); 
		rnjpanelTabs .add (tsc) ; 
		m_panelPanels.add(strNameCopy, compPanel); 
		// Если это первый компонент, его надо выбрать. 
		if(l == m_hashNameToTab.size() ) 
			selectTab (strNameCopy); 
		return true; 
	}
	public boolean removeTab(String strName) { 
		// Проверка того, что имя метки не используется. 
		Component tsc = (Component)m_hashNameToTab.get(strName) 
		Component compPanel = (Component)m_hashNameToCompPanel.get(strName) 
		if (null == tsc) 
			return false; 
		// Удаление хеш-списков, меток и окна сотр. 
		m_hashNameToTab.remove(strName); 
		m_hashNameToCompPanel.remove(strName); 
		m_panelTabs.remove(tsc); 
		m_panelPanels.remove(compPanel); 
		// Выбор второго компонента, если выбор первого уже 
		// сделан. Произвольный выбор первого имеющегося имени 
		// в хеш-таблице имен, 
		if(m_strSelectedTab == strName) { 
			m_strSelectedTab = null; 
			Enumeration enumKeys = m_hashNameToTab.keys() ; 
			if(enumKeys.hasMoreElements() ) { 
				String strNewSelectedTab = 
                                                                             (String)enumKeys.nextElement(); 
				selectTab(strNewSelectedTab); 
			}
		}
		return true; 
	} 


    1O2	Visual J++: основы программирования 

	public boolean selectTab(String strName) { 
		// Если эта метка уже выбрана, данный вызов игнорируется, 
		if(strName == m_strSelectedTab) 
			return true; 
		// Получение компонента, возврат значения false, если 
		// компонента с этим именем нет. 
		TwoStateComp tsc = (TwoStateComp)m_hashNameToTab.get(strName) 
		if ( null == tsc )     
			return false; 
		// Получение выбранного компонента вкладки. Отмена выбора. 
		TwoStateComp tscOldSelectedTab = 
		    (TwoStateComp)m__hashNameToTab.get(m_strSelectedTab); 
		if ( null != tsc ) 
			tsc.setState( 0 ); 
		// Выбор новой вкладки, перенос окна comp вперед, 
		tsc.setState( 1 ) ; 
		m_cardLayoutForPanels.show(m_panelPanels, strName); 
		m_strSelectedTab = new String(strName); 
 		// Передача контейнеру события, показывающего новый выбор. 
		Event evt = new Event (this, Event .ACTION_EVENT, strName); 
		Container parent = getParent () ;   
		if (null != parent) 
			postEvent( evt ); 
		return true;     
	} 
	public   String  getSelectedTabName()    { 
		return  new  String(m_strSelectedTab) ; 
	} 
	public  Component  getSelectedTabPanel()    { 
		return   ( Component )m_hashNameToCompPanel.get(m_strSelectedTab), 
	) 
   }

    Событие ACTION_EVENT обычным образом, описанным в предыдущих параграфах 
этой главы, передается классу TabbedPanel всякий раз, когда пользователь 
выбирает один из объектов TwoStateComp, являющихся вкладками. TabbedPanel 
передает другое событие ACTION_EVENT своему родительскому контейнеру, когда 
пользователь выбирает новую вкладку. Далее показан текст метода action()
обработчика событий: 

    public boolean action (Event evt, Object what) { 
	// Если событие связано с одним из объектов 
	// TwoStateComps,  надо найти связанную с ним 
	// ключевую строку и выбрать ее. 
	if (!(evt.target instanceof TwoStateComp)) 
		return false; 
	TwoStateComp tsc = (TwoStateComp)evt.target; 
	if ( !m hashNameToTab.contains( tsc ) ) 


    Глава 3. Основы GUI	103 

		return false; 
	Enumeration enumKeys = m_hashNameToTab.keys(); 
	String strName = null; 
	for( strName = null; enumKeys.hasMoreElements() ; )  { 
		String str = (String) enumKeys.nextElement(); 
		if(tsc == m_hashNameToTab.get(str))  ( 
			strName = str; 
			break; 
		}
	} 
	selectTab(strName) ; 
	Event evt2 = new Event(this, Event.ACTION_EVENT, strName); 
	getParent() .postEvent(evt2) ; 
	return true; 
   }

   / * ******** 
     * Private-переменные экземпляра. 
     ******** * / 
   private Panel	m_panelTabs; 
   private Panel	m_panelPanels; 
   private CardLayout	m_CardLayoutForPanels; 
   private Hashtable	m_hashNameToTab; 
   private Hashtable	m_hashNameToCompPanel; 
   private String	m_strSelectedTab; 
}

    Резюме 

    Таким образом, графические пользовательские интерфейсы составлены из 
объектов типа Component, расположенных в порядке иерархии. Каждый компонент 
должен находиться в контейнере. Контейнер — это особый тип компонентов, 
обладающий способностью содержать другие компоненты. Причем каждый контейнер, 
будучи компонентом, должен находиться в другом контейнере. В иерархии окон 
корневым является либо главное окно приложения, либо диалоговое окно. 
Контейнеры верхнего уровня не должны содержаться в других контейнерах. 
    Менеджер компоновки — это объект, который располагает компоненты в 
контейнере, используя особый алгоритм или определенные правила. В библиотеках 
Java имеется несколько менеджеров компоновки общего назначения, основанных на 
различных алгоритмах компоновки. К стандартным менеджерам компоновки относятся 
FlowLayout, BorderLayout, CardLayout, GridLayout и GridBagLayout. Большинство 
интерфейсов могут быть представлены в виде иерархии окон, в каждом из которых 
используется один из 

    104	Visual J++: основы программирования 

стандартных менеджеров компоновки. ResourceWizard включает в себя еще один 
менеджер компоновки — DialogLayout, располагающий компоненты с заданной длиной 
и шириной согласно указанным координатам. Данный менеджер выражает размеры 
граничных прямоугольников в "диалоговых" единицах. Зачастую, особенно если не 
известен полный размер контейнера, бывает предпочтительным заменить 
DialogLayout более динамическим менеджером. В некоторых случаях удобнее 
убрать, менеджер компоновки и располагать компоненты вручную, используя методы 
move(), resize() и reshape(). Наряду со стандартными можно создавать и 
собственные пользовательские менеджеры компоновки. 
