JavaBean-компоненты, пакеты и Java 2D

В

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

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

Программа, которую мы будем тщательно разбирать в этой главе, называется DrawCiock02, Как и в большинстве случаев, ее можно найти в сопровождающих кни­гу материалах. Эта программа связана с содержимым каталога Arc, Который также входит в состав сопровождающих материалов. Как результат, вы не сможете запус­тить данную программу, не установив код из структуры каталогов DrawClock02 И из структуры каталогов Src. Не стоит и говорить, что если вы не сохраните то же отно­шение между двумя этими каталогами, программа без внесения в нее изменений за­пускаться не будет, стало быть, будьте внимательными.

Создание версии 1.0 часов

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

■ При рисовании символов будет использоваться Graphics2D — для придания
часам вида жидкокристаллического табло с закругленными краями цифр.

■ Размещение и вид часов будут управляемыми — можно будет изменять размер жидкокристаллических цифр и устанавливать положение часов внутри эле­мента управления.

■ Часы перенесены в свой собственный пакет — com.ЕIvenware.Comp. clock.

То, что мы довели элемент управления до версии 1.0, вовсе не значит, что его нельзя сделать еще лучше. Скорее, это говорит, что пока сойдет такой.

Перемещение файлов в новый пакет

Давайте начнем с того, что поместим компонент в пакет, где он будет доступен другим программам. Это сложная, но очень важная процедура. Ведь сама суть Java во многих аспектах как раз и заключается в создании многократно используемых компонентов. Если компоненты не сохранены в каталоге, через который все ваши проекты смогут до них добраться,— они не являются многократно используемыми. То, что вы сейчас будете читать, может оказаться несколько сложным, однако, ра­зобравшись в этой теме, вы получите ключ к многократному использованию компо­нентов в своих программах.

В этой книге мы помещаем все глобально видимые классы в пакет, являющийся частью дерева com. elvenware.*. Если идея помещения компонента в пакет вам ка­жется бессмысленной, перечитайте главы части V, в частности, особое внимание уделите главе 25.

Если корневой каталог с исходным кодом примеров этой книги называется jbook, нам нужно поместить эти файлы сюда:

∕src∕com∕elvenware∕comp∕clock

Текущим же местонахождением наших файлов с исходным кодом может быть, например, такой каталог:

∕src∕DrawClock∕src∕drawclock

примечаниеЗдесь вы найдете файлы ElfClock. java и DrawClock. java, созданные в преды­дущей главе. Эти и есть те файлы, которые необходимо переместить.

Совершенно не важно, в каких каталогах сейчас хранятся файлы ElfClock. java и DrawClock. java; также не имеет особого значения то, куда вы бу­дете их перемещать. Основная мысль здесь состоит в том, что иногда может существо­вать код, который должен быть доступен из нескольких программ. Для этого зачастую проще всего взять файлы с исходным кодом из дерева каталогов вашего текущего про­екта и переместить в каталог, который вы можете сделать глобально доступным для од­ного или нескольких проектов. Важна задача в общем, а конкретные имена каталогов значения не имеют.

Код компонента Clock должен находиться в пакете com. elvenware. comp. clock. C помощью инструментов JBuilder переместите свои существующие классы в пакет с именем clock. На вашей машине путь к этому каталогу может выглядеть так:

∕src∕com∕elvenware∕comp∕clock

Опять-таки, конкретный каталог, в который будут помещаться файлы, не важен; только не забудьте убедиться, что каталоги com, eivenware, Сотр И clock существуют.

В частности, можно начать с перемещения ElfClock. java и DrawClock. java в ∕src∕com∕elvenwarβ∕comp∕clock. Внутри своих файлов с исходным кодом вы ука­жете это с помощью следующего оператора пакета:

Package com. elvenware. comp. clock

Самый простой способ переместить файлы с исходны.»; кодом для одного класса из одного месторасположения в другое — это перейти в панель проекта, щелкнуть
правой кнопкой мыши на имени файла, который вы хотите переместить, и выбрать Rename (Переименовать) в появившемся контекстном меню. На экране появится диалоговое окно Rename (Переименование), показанное на рис. 36.1.

В данном случае, если вы хотите переместить файл из одного каталога в другой, не трогайте поля File Name (Имя файла) и File Туре (Тип файла), которые находятся в нижней части диалогового окна (см. рис. 36.1). Вместо этого с помощью дерева ка­талогов, расположенного над ними, найдите то место, куда необходимо перемес­тить файл. Если вам понадобится создать новые каталоги, воспользуйтесь предназ­наченной для этой цели пиктограммой, которая находится в верхнем правом углу диалогового окна Rename. Создав нужные каталоги и добравшись до нужного мес­та, щелкните на кнопке OK, и файл с исходным кодом будет перемещен.

примечаниеНа данном этапе вам, пожалуй, нужно отредактировать операторы пакета в начале каждого из файлов. В частности, теперь они должны выглядеть так: Com.Elvenware.Comp.Clock.

примечаниеЯ обнаружил, что утилиты корректировки связей в редакциях SE и Enterprise не годятся для перемещения файлов так, как предложено здесь. Если у вас получилось — замечательно, но я нашел, что корректировка связей не работает как следу­ет, когда требуется переместить файлы в каталоги, расположенные вне области текуще­Го проекта. Вместо него я бы воспользовался простым приемом, описанным выше.

Следует также добавить, что существует еще несколько способов ре­шения проблемы, исследованной мною в данном разделе главы. Например, в Java можно иметь пакет с именем com. elvenware.* в нескольких местах жесткого диска. Вообще говоря, все, что вам потребуется сделать, — это поместить базовый каталог для всех мест в свой путь классов, а остальные проблемы Java решит автоматически. Или же можно хранить исходный код в нескольких каталогах и попросить Java помещать скомпилиро­Ванные файлы с расширением .Class в один каталог.

Я не хочу сказать, что такие системы полностью бесполезны; бывают случаи, ког­да они могут оказаться наилучшим решением, в частности, если вы работаете в со­ставе большой команды, однако в данном случае мне это решение не подходит.

рис. з^л. диалоговое окно rename позволяет не только переименовать, но и переместить файл с одного местоположения в другое

Проблемы путей: создание файлов в новом каталоге

После того, как вы переместили свои файлы, вам нужно разобраться с двумя воп­росами:

■ Как сделать так, чтобы новый каталог был в пути классов для вашего текущего проекта?

■ Как гарантировать, что все новые файлы, которые вы захотите добавить в ком­понент Clock, окажутся в требуемом каталоге?

Как вы знаете из части V, поместить пакеты в путь к исходному коду JBuilder можно с помощью диалогового окна Project Properties (Свойства проекта), которое показано на рис. 36.2.

На рис. 36.2 каталог, В Который Я переместил DrawClock. Java И ElfClock. Java, Теперь упомянут в пути к исходному коду. Следующий шаг — добавить соответству­ющий оператор импорта В Framel. Java:

Inport Com.Elvenware.Comp.Clock.[††] [‡‡];

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

К сожалению, пути, показанные на рис. 36.2, не будут в точности соот­ветствовать именам на вашей машине. На моей машине два упомянутых класса распо­ложены в следующем каталоге: G:\srcjava\jbbook\src\personal\com\elvenware CompClock.

примечаниеНа рис. 36.3 видно, что исходный пакет Com.Elvenware.Comp.Clock Теперь счи­тается частью вашего проекта. Мне следует подчеркнуть, что здесь речь идет о фай­лах с исходным кодом, а не о class-файлах. На данном этапе мы не пытаемся выяс­нить, где будут располагаться class-файлы. Собственно говоря, они окажутся в ва­шем локальном проекте. Однако для нас это не важно: когда это станет важным, я покажу, как разобраться с этим с помощью JAR-файлов.

Возможно, у вас уже голова кружится от попыток понять, что же проис­ходит со всеми этими путями к исходному коду и путями классов. Кажется, я уже упоми­нал, что для Java пакеты — в некотором роде то же, что указатели для C и C++. Это как раз те трудности, которые вам потребуется преодолеть. Но как только вы поймете, что можно делать с путями в Java, то обнаружите что пакеты, как и указатели, — инструмент Исключительно полезный.

Следует сделать несколько замечаний по поводу рисунка 36.2. На нем хорошо то, что вы можете видеть код, указывающий на каталог JbbookSrcPersonal. Именно там я храню на своей машине компоненты Elvenware:

п°имечаниеJbbookSГс/регSonalСот/еIvenwaceCodeboxJbbookSrcPersonalComElvenwareCompClock

В сопровождающих книгу материалах, в каталоге ∕jbbook∕arc при­сутствуют две группы подкаталогов. Первая, названная personal, содержит код, который будет компилироваться в редакции Personal. Вторая — pro — будет компилироваться в ре­дакциях SE и Enterprise. Я сделал это для того, чтобы предоставить исходный код для всех версий JBuiIder. Более подробную информацию по этому вопросу вы сможете Найти в файле Index.Html в тех же сопровождающих материалах.

Рис. 36.4. Указание SrcPersonal В качестве каталога по умолчанию, в который будет сохраняться или перемещаться исходный код

Здавать JavaBean-компоненты — это описывается в других книгах. Данная книга на­целена на собственно JBuilder, и все эти перетасовки кода и пакетов приводятся здесь для того, чтобы вы четко понимали, как это делается.

Хотелось бы подчеркнуть, что такая перетасовка пакетов весьма часто встречает­ся в разработке проектов на Java. Собственно говоря, одна из школ, носящая назва­ние экстремального программирования, придает очень большое значение именно такой корректировке связей, которая описывалась на протяжении нескольких пос­ледних страниц. Перемещение классов из пакета в пакет — навык, который должен быть в багаже знаний большинства разработчиков.

Остаток этой главы посвящен рассмотрению способов создания интерфейса для нового компонента часов. Информация, которую вы здесь найдете, поможет вам в создании "сердца" bean-компонента. Однако вообще же задача эта сводится не бо­лее чем к задаче программирования на Java, и поэтому представляет лишь второсте­пенный интерес для читателей книги. Если вам не терпится идти дальше, вы може­те пропустить материал вплоть до следующей главы и перейти к изучению способов анализа созданного вами bean-компонента и упаковки его в JAR-файл. Если же вы предпочитаете более тщательно рассмотреть весь процесс создания свойств и ин­терфейса bean-компонента — продолжайте чтение.

Совершенствуем интерфейс часов

pj» r∙*w lit ⅛10:ее:eoИтак, вы, наконец, готовы приступить к работе над второй фазой создания ком­понента часов. Сейчас наша цель — усовер­шенствовать интерфейс компонента так, что­бы он выглядел, как показано на рис. 36.5.

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

Вы обнаружите, что исходный код ElfClock. java практически идентичен тому, ка­ким он был в первой версии данной программы. Отличие состоит в добавлении нового класса, DrawPieceClθck02. java, который является производным от простого класса DrawClock, а с ним-то мы уже знакомы. Фрагменты исходного кода этого класса пока­заны в листинге 36.1. В листинге 36.2 приведен новый исходный код Eifcioek. java.

Листинг 36.1. Исходный код класса DrawPieceClock02 показывает, как создать

С помощью Java2D цифры на жидкокристаллическом табло

/** Объект Shape, Используемый при рисовании символа */

Private GeneralFath gp;

/** Конструктор По Умолчанию Создает GeneralFath */

Public DrawPieceClock02()

<

Gp = new GeneralPath(GeneralPath. WIND_EVEN_ODD);

}

∕**

* Рисуем символ и сбрасываем (очищаем) путь для следующего

* Символа */

Private void doDraw()

<

G2.draw(gp); Gp. reset() ;

}

∕**

* Даем эту команду, чтобы нарисовать часы. Кроме конструктора и

* Методов чтения и записи, это единственная общедоступная команда,

* Выполняемая над объектом.

*

* Sparam initG2 Графический объект, с помощью которого будем закрашивать */

Public void draw(Graphics2D initG2)

(

Calendar С = new GregorianCalendar();

∕∕ Нужно получить каждую цифру в показании времени, которое будем // рисовать. Например, в 12:34:56, 1 — старший час (bigHour),

Il 2 — младший (SmallHour) И т. д.

Int seconds = С.Get(Calendar. SECOND);

Int smallSec = seconds % 10;

Int bigSec = seconds ∕ 10;

Int minutes = c. get(Calendar. MINUTE);

Int smallMin = minutes % 10;

Int bigMin = minutes ∕ 10;

Int hours = c. get(Calendar. HOUR_OF_DAY); int smallHour = hours % 10; int bigHour = hours I 10; g2 = initG2;

G2.setColor(foreGround);

∕∕ Задаем ширину и форму краев линий в цифрах

G2.setStroke(new BasicStroke(textWidth, BasicStГоке.CAP_ROUND,

BasicStroke. JOIN_ROUND));

PrintNum(bigHour, O) ;

PrintNum(smallHour, 1) ; Colon(2);

PrintNum(bigMin, 3) ;

PrintNum(smallMin, 4) ; Colon(5);

PrintNum(bigSec, 6) ; printNum(smallSec, 7) ;

)

∕**

* Рассчитываем координаты в пикселях линий, которые нужно нарисовать,

* Чтобы составить каждый из символов. Каждая часть символа —

* Это отдельная черта;

* Чтобы составить вместе отдельные части и нарисовать символ целиком,

* Используем GeneralPath.

*

* Sparam position: Конкретная черта ■ символе.

* Sparam place: Место во времени; так, в 12:34:56 Тройка находится

* На четвертом месте.

*/

Private void drawBar(ClockPosition position, int place)

{

Int addin = getSpace(place); ∕∕ place * (cħarWidth + textWidth + 2);

Int XPosStart ≈ IeftGap + addin;

Int xPosEnd = CharWidth + addin;

Int yPosTop = HORZ_Y_TOP + topOffSet;

Int УPosMiddle = HORZ_Y_MIDDLE + topOffSet;

Int УPosBottom = HORZ_Y_BOTTOM + topOffSet;

If (position = H_TOP)

{

Line2D 1 = new Line2D. Double(xPosStart, УPosTop, xPosEnd, yPosTop); gp. append(1, true);

)

Else if (position == H_TOP_BACK)

{

Line2D 1 = new Line2D. Double(xPosEnd, УPosTop, xPoβStart, yPosTop); gp. append(1, true);

}

Else if (position == H_MIDDLE)

{

Line2D 1 = new Line2D. Double(xPosStart, yPosMiddle,

XPosEnd, yPosMiddle);

Gp. append(1, true);

)

Else if (position ≈ H_MIDDLE_BACK)

(

Line2D 1 = new Line2D. Double(xPosEnd, yPosMiddle,

XPosStart, yPosMiddle);

Gp. append(1, true);

)

Else if (position ≈ H_BOTTOM)

{

Line2D 1 = new I>ine2D. Double (xPosStart, yPosBottom,

XPosEnd, yPosBottom);

Gp. append(1, true);

)

Else if (position ≈ V_LEFT_TOP)

(

Line2D 1 = new Line2D. Double(xPosStart, УPosTop,

XPosStart, yPosMiddle);

Gp. append(1, true);

)

Else if (position == V_LEFT_BOTTOM)

{

Line2D 1 = new Line2D. Double(xPosStart, yPosMiddle, XPosStart, yPosBottom);

Gp. append(1, true);

)

Else if (position == V_MIDDLE)

(

Line2D 1 = new Line2D. Double(xPosEnd — (charWidth ∕ 2), УPosTop, XPosEnd — (charWidth /2) , yPosBottom);

Gp. append(1, true);

)

Else If (position == V_RIGHT_TOP)

уposmiddle);yposbottom);yposbottom);(

Line2D 1 = new Line2D. Double(xPosEnd, УPosTop, xPosEnd, gp. append(1, true);

)

Else if (position == V_RIGHT_SIDE)

{

Line2D 1 = new Line2D. Double(xPosEnd, УPosTop, xPosEnd, gp. append(1, true);

)

Else if (position ≈ V_RIGHT_BOTTOM)

{

Line2D 1 ≈ new Line2D. Double(xPosEnd, УPosMiddle, xPosEnd, gp. append(1, true);

)

)

У *

* Рисуем отдельную цифру на экране. Например, в

* 12:34:56 Рисуем цифру 1 Или цифру 6.

* Sparam num Цифра, которую будем рисовать

* Sparam place Позиция в показании времени — BigHour, SmallHour И т. д. */

Private void printNum(int num, int place)

(

Switch (num)

(

Case O:

Zero(place); break;

Case 1:

One(place); break;

∕∕ Повторяющийся код для Case 3-case 9 Опущен.

∕∕ Полный листинг см. в сопровождающих материалах, Default:

System. err. println("Error"); break;

)

DoDraw();

)

∕**

* При смещении цифры на третье или четвертое место здесь рассчитываем,

* На сколько ее сместить.

*

* Sparam place Место в показании времени; так, в показании 12:34:56

* Двойка находится на втором месте

*

* Sreturn Смещение в пикселях */

Private int getSpace(int place)

{

Return ((CharMidth + te×tWidth +2) * Place) + IeftOffSet;

)

∕**

* Рисуем двоеточие

*

sparam place позиция цифры.*

*/

Private void colon(int place)

{

Int pos = getSpace (place) ; int space = 2;

Int x ≈ (charWidth ∕ 2) + pos;

Int yl = HORZ_Y_TOP + 10 + topOffSet;

Int y2 ≈ HORZ_Y_BOTTOM — space + topOffSet;

G2.drawRect(x — space, yl — 5, space, space);

G2.dra¼Rβct(x — space, y2 — 5, space, space);

DoDxaw() ;

}

∕**

* Рассчитываем линии в куле.

*

* Gparam place Место в показании времени; так, в 12:34:56 Тройка

* Находится на четвертом месте */

Private void zero(int place)

{

DrawBar(V_LEFT—BOTTOM, place);

DrawBar(V_LEFT_TOP, place);

DrawBar(H_TOP, place);

DrawBar(V_RIGHT_TOP, place);

DrawBar(v_rightjbottom, Place);

DrawBar(H_BOTTOM, place);

}

∕**

* Рассчитываем линии в единице

*

* 6param place Место во времени; так, в 12:34:56 3 Тройка находится

* На четвертом месте */

Private void one(int place)

{

DrawBar(V_MIDDLE, place);

}

∕∕ B Тексте книги я опускаю код, реализующий остальные методы.

// Полный исходный код можно найти в сопровождающих материалах, Private void two(int place)

Private void three(int place)

Private void four(int place)

Private void five(int place)

Private void six(int place)

Private void seven(int place)

Private void eight(int place)

Private void nine(int place)

Public Color getForeGround()

Public void SetForeGround(Color foreGround)

Public int getTextWidth()

Public void SetTextWidth(int textwidth)

Public int getCharWidth()

Public void SetCharWidth(int charWidth)

Public int getCharHeight()

W

* Задавая высоту символа, нужно удостовериться, что

* Значения, заданные в качестве верхней, средней и нижней части

* Символа находятся в соответствии с его высотой.

*

* Sparam CharBeight Высота символа */

Public void SetCharHeight(int CharHeight)

{

This. CharHeight = CharHeight;

HORZ_Y_MIDDLE = HORZ_Y_TOP + this. CharHeight; HORZ_Y_BOTTOM ≈ HORZ_Y_MIDDLE + this. CharHeight;

}

Public int getTopOffSet()

Public void βetTopOffSet(int top)

Public void SetLeftOffSet(int IeftOffSet)

Public int getLeftOffSet()

Public int getLeftGap()

Public void SetLeftGap(int IeftGap)

)

Листинг 36.2. Исходный код EIfCIocLjava был слегка модифицирован по сравнению с предыдущей главой, чтобы обеспечить доступ к классу DrawPieceClock02

Public ElfClock()

{

Try

{

Jbɪnit () ;

}

Catch(Exception ex)

{

Ex. printStackTrace();

J

)

∕**

* Пользовательский метод, позволяющий JBuilder Инициализировать

* Визуальные компоненты

*

* 0throws Exception

*/

Private void jblnit() throws Exception

{

This. SetLayout(borderLayoutl);

ClockTimer = new javax. swing. Timer(250, new ActionListenerO (

Public void actionPerformed(ActionEvent e)

{

Repaint () ;

}

));

)

∕**

* Свойство, позволяющее включать/выключать часы

*

* Gparam running Остановить или включить часы?

*/

Public void SetRunning(boolean running)

{

This. running ≈ running; if (this. running)

ClockTimer. start(); else

ClockTimer. stop();

)

∕**

* Определяет, Активны ли часы

*

* 0return Часы работают или нет?

*/

Public boolean isRunning()

(

Return running;

)

Public void paint(Graphics g)

{

Super. paint(g); g. setColor(getBackground());

G. fillRect(O, O, this. getWidth(), this. getHeight()); drawClock. SetForeGround(getForeground()); drawClock. SetTextWidth(textWidth); drawClock. SetCharWidth(charWidth); drawClock. SetCharHeight(CharHeight);

DrawClock. setTopOffSet(topθffSet); DrawClock. SetLeftOffSet(IeftOffSet); DrawClock. setLeftGap(IeftGap); DrawClock. draw((Graphics2D)g);

}

Public void SetTextWidth(int textWidth)

{

This. textWidth = te×tWidth;

}

Public int getTextWidth()

<

Return textWidth;

}

Public void SetCharWidth(int charWidth)

{

This. charWidth = charWidth;

}

Public int getCharWidth()

{

Return charWidth;

)

Public void SetCharHeight(int CharHeight)

F

This. CharHeight = CharHeight;

)

Public int getCharHeight()

{

Return CharHeight;

)

Public void SetLeftOffSet(int left)

<

This. IeftθffSet = left;

)

Public int getLeftθffSet()

{

Return IeftOffSet;

)

Public void SetTopOffSet(int top)

{

This. topθffSet = top;

}

Public int getTopOffSet()

{

Return topOffSet;

)

Public void setLeftGap(int IeftGbp)

This. IeftGap = IeftGap;

}

Public int getLeftGap()

{

Return IeftGap;

)

Public void SetForeground(java. awt. Color foreground) {

Super. SetForeground(foreground);

) public j ava. awt. Color getForeground()

{

Return super. getForeground();

}

Public void SetBackground(java. awt. Color background) {

Super. SetBackground(background);

}

Public java. awt. Color getBackground()

{

Return super. getBackground();

}

Просматривая листинг 36.2, обратите внимание, что переменная, которая рань­ше указывала на класс DrawClock, Теперь указывает на DrawPieceClock:

Private DrawPieceClock02 drawClock = new DrawPieceClock02();

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

Public void paint(Graphics g)

{

Super. paint(g); g. SetColor(getBackground());

G. fillRect(O, O, this. getWidth(), this. getHeight()) ;

DrawClock. SetForeGround(getForeground());

DrawClock. SetTextWidth(textWidth);

DrawClock. SetCharWidth(CharWidth);

DrawClock. SetCharHeight(CharHeight);

DrawClock. SetTopOffSet(topθffSet);

DrawClock. SetLeftOffSet(IeftOffSet);

DrawClock. setLeftGap(IeftGap);

DrawClock. draw((Graphics2D)g);

}

Как видите, большинство свойств фактически реализовано в DrawPieceClock. Я создаю для них методы-упаковщики в Elfdock, чтобы они могли быть выявлены в форме свойств в инспекторе JBuilder. В частности, взгляните на рис. 36.6, на кото­ром показаны видимые свойства новой версии компонента ElfClock.

Из рис. 36.6 видно, что bean-компонент ElfClock Остается внутри потомка JFrame. Компонент Eifdock выбран в панели структуры, и в инспекторе можно видеть общедоступные свойства компонента. Я подавил все свойства предка тем, ЧТО Не перекрыл реализацию GetAdditionalBeanlnfo B SinpleBeanInfo. Как ВЫ Помните, этот метод по умолчанию возвращает Null. Значение Null, Возвращенное этим методом объекта Beaninfo, Дает инспектору указание не производить над bean-компонентом дальнейшего самоанализа, а показать общедоступные свойства, предъявляемые самим классом Beaninfo.

рис. зб.б. в своей новой ипостаси компонент elfclock имеет набор новых свойств, которые позволяют настраивать внешний вид часов

Рисование цифр на жидкокристаллического табло с помощью Graphics2D

В этом разделе я расскажу, как рисовать цифры в рамках усовершенствованных часов. Это сложный процесс, который я изолирую в отдельном объекте с именем DrawPieceClock02. java. C точки зрения использования памяти выделение части EifCiock В этот отдельный "рисующий" объект обошлось мне относительно не до­рого. Как бы там ни было, я считаю, что благодаря этому код стал читабельнее, и, к тому же, удалось изолировать определенную степень сложности. В частности, все рисование часов выполняется в одном этом объекте, в то время как все прочие зада­чи поддержки, например, построение таймера, за счет которого работают часы, ре­шаются в ElfClock. Это типичная стратегия типа "разделяй и властвуй"

Во время рисования символов я применяю объект GeneralPath, Который входит в состав Java2D. Первым делом я создаю GeneralPath В конструкторе:

Gp = new GeneralPath(GeneralPath. WIND_EVEN_ODD);

Реальная же работа затем выполняется внутри метода draw, который и управляет объектом:

Pτιblic void draw (Graphics2D initG2)

{

Calendar С ≈ new GregorianCalendar();

// Нужно получить каждую цифру в показании времени, которое будем // рисовать. Например, в 12:34:56, 1 — старший час (bigHour) ,

// 2 — младший (SmallHour) И т. д.

Int seconds ≈ С.Get(Calendar. SECOND);

Int smallSec = seconds % 10;

Int bigSec = seconds ∕ 10;

Int minutes — c. get(Calendar. MINUTE);

Int smallMin ≈ minutes % 10;

Int bigMin = minutes ∕ 10;

Int hours = c. get(Calendar. HOUR_OF_DAY); int smallHour ≈ hours % 10;

Int bigHour = hours /10; g2 = initG2;

G2.setColox(foreGround);

// Задаем ширину и форму краев линий в цифрах

G2.SetStroke(new BasicStroke(textWidth, BasicStroke. CAPeROUND, BasicStroke. JOINJROUND));

PrintNum(bigHour, О); PrintNum(smallHour, 1); Colon(2);

PrintNum(bigMin, 3); printNum (smallMin, 4); colon(5);

PrintNum(bigSec, 6); printNum(smallSec, 7);

>

Первым делом код создает объект Calendar. Затем из календаря он получает се­кунды, минуты и часы. Обратите внимание, что время получается в 24-часовом фор­мате с использованием Calendar.Hour_of_day вместо Calendar.Hour. (Последняя константа получает время в двух 12-часовых сегментах суток, а не в одном 24-часо­вом сегменте.)

Следующий шаг состоит в том, чтобы преобразовать секунды, минуты и часы в отдельные цифры. Например, если сейчас 23 минуты какого-либо часа, программе нужно разделить это число на две цифры, 3 и 2. Затем она рисует каждую цифру от­дельно, как было показано выше на рис. 36.5.

На рис. 36.5 видны гладкие, закругленные края — верх единицы, начала и концы двоек, углы нуля. Эти эффекты достигаются посредством вызовов функций Graphics2D. В частности, для используемого нами экземпляра Graphics2D форма линии задается константами cap_round и join_round:

G2.setStroke(new BasicStroke(textWidth, BasicStroke. CAPeROUND, BasicStroke. JOIN_ROUND));

примечаниеТеперь линия будет иметь закругленные края, как показано на рис. 36.5.

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

Все, что осталось сделать на данном этапе, — это создать отдельные цифры и за­тем нарисовать их. Каждая цифра появляется на свет в отдельном методе. Напри­мер, цифра О Создается в следующем методе:

Private void zero (int place)

{

DrawBar(VeLEFTJBOTTOM, place); drawBar(VeLEFTJTOP, place); drawBar(HeTOP, place); drawBar(VeRIGHTeTOP, place); drawBar(VeRIGHTeBOTTOM, place); drawBar(HeBOTTOM, place);

псевдо-перечислимые типы, такие, как v_left_bottom, обозначают каждую из линий, из которых состоит цифра. чтобы создать символ, вы должны скомбинировать одну или несколько таких линий, как показано на рис. 36.7.
каждый вызов drawbar приводит к тому, что в generaipath помещается новая линия:
private void drawbar(position position, int place)
⅛τop
vjaehttor
vjaghr.botron
kbotton
рис. 36.7. шесть линий, из которых создается цифра 0 на жидкокристаллическом табло

Int addɪn = getSpАсе (place) ; ∕∕ place * (charWidth + textWidth + 2) ;

Int XPosStart = IeftGap + addin;

Int xPosEnd = charWidth + addin;

Int yPosTop = HORZ_Y_TOP + topOffSet;

Int yPosMiddle = HORZ_Y_MIDDLE + topOffSet;

Int yPosBottom ≈ HORZ_Y_BOTTOM + topOffSet;

If (position ≈ H_TOP)

{

Line2D 1 = new Line2D. Double(xPosStart, УPosTop, XPosEnd, yPosTop);

Gp. append(1, true);

)

Else if (position == H_TOP_BACK)

{

Line2D 1 = new Line2D. Double(xPosEnd, УPosTop, XPosStart, yPosTop);

Gp. append(1, true);

)

Else if (position == H_MIDDLE)

И ®.д.

Из этого фрагмента кода видно, что каждая новая линия создается с помощью объекта Line2D из пакета Java. awt. geom. В некоторых случаях важное значение бу­дет иметь, рисуется линия слева направо или справа налево. Для таких случаев я оп­ределил две перечислимых константы. Например, в этом коде вы можете видеть н_тор и н_тор_васк. Одна соответствует рисованию справа налево, а вторая — слева направо. Другие константы соответствуют рисованию сверху вниз и снизу вверх. У меня также есть специальная линия для рисования единицы.

Метод DrawBar Начинает с некоторых простых вычислений для определения надлежащего месторасположения каждого символа. Здесь необходимо уделить вни­мание нескольким деталям. Для примера рассмотрим показание времени 12:34:56. В этот момент времени цифру 2 следует нарисовать справа от цифры 1, а цифру 3 — справа от двоеточия, цифры 1 и цифры 2. Эти смещения вычисляются в начале DrawBar. Пользователю компонента предоставляется также возможность вставить смещение, или поле, вокруг изображения часов. Переменные topOffSet и Addin Позволяют учитывать упомянутый фактор.

Как только все линии, составляющие цифру, присоединены к GeneralPath, Все, что остается сделать, — это нарисовать цифру:

Private void doDraw()

{

G2 .draw(gp) ; gp. reset() ;

}

B приведенном выше более чем простом вызове сначала с помощью объекта Graphics2D рисуется GeneralPath. Этот ВЫЗОВ Работает потому, что GeneralPath является потомком объекта shape, а все объекты shape могут рисоваться методом Graphics2D. draw. Более того, все геометрические объекты Graphics2D, включая Line2D, также являются потомками shape. Следовательно, любой объект Graphics2D можно нарисовать с помощью метода объекта shape.

Я не буду вдаваться в ненужные подробности, описывая различные свойства, до­бавленные мною в компонент Clock. Тем не менее, при использовании компонента следует обратить внимание на свойства topθffSet, IeftoffSet, textwidth, CharHeight и CharWidth. Например, свойство textWidth позволяет определить толщину линий, которые используются для рисования цифр в часах. Свойства CharWidth и CharHeight определяют высоту и ширину рисуемых цифр.

Самый лучший способ узнать, что представляют собой эти свойства, — это ак­тивно поработать с ними в среде JBuilder. Просто присваивайте им различные зна­чения, и вы быстро поймете, для чего предназначено то или иное свойство. Можно также взглянуть на код метода paint для ElfClock, где задаваемые пользователем значения этих свойств передаются объекту DrawPieceClock02, который фактически проделывает всю тяжелую работу по рисованию часов.

Перечислимый тип

Вас, наверное, заинтересовали константы — например, н_тор и h_middle, — ко­торые были использованы в этой программе. Как вам известно, в Java отсутствует понятие перечислимого типа. Из-за этого я вынужден был прибегнуть к небольшой хитрости, которая позволила создать нечто вроде перечислимого типа. В главе 16 я уже объяснял такой синтаксис.

Вот класс, с помощью которого объявляется псевдо-перечислимый тип: public final class ClockPosition {

Private ClockPositionO {}

Public static final ClockPosition H_TOP = new ClockPositionO; public static final ClockPosition H_TOP_BACK = new ClockPositionO; public static final ClockPosition H_MIDDLE = new ClockPositionO; public static final ClockPosition H_MIDDLE_BACK = new

ClockPositionO ;

Public static final ClockPosition H_BOTTOM = new ClockPositionO; public static final ClockPosition V_LEFT_TOP = new ClockPositionO! public static final ClockPosition V_LEFT_BOTTOM = new

ClockPositionO ;

Public static final ClockPosition V_MIDDLE = new ClockPositionO; public static final ClockPosition V_RIGHT_TOP = new ClockPositionO ; public static final ClockPosition V_RIGHT_SIDE = new ClockPositionO; public static final ClockPosition V_RIGHT_BOTTOM = new

ClockPositionO ;

}

А вот пример использования этого класса:

Private com. elvenware. comp. clock. ClockPosition V_RIGHT_SIDE ≈ com. elvenware. comp. clock. ClockPosition. V_RIGHT_SIDE;

Private com. elvenware. comp. clock. ClockPosition V_RIGHT_BOTTOM = com. elvenware. comp. clock. ClockPosition. V_RIGHT_BOTTOM;

∕∕ Здесь опущен код

Private void drawBar(ClockPosition position, int place)

{

∕∕ Здесь опущен ход If (position = V_RIGHT_SIDE)

{

Line2D 1 = new Line2D. Double(xPosEnd, уPosTop, xPosEnd, yPosBottom); gp. append(1, true);

}

Else if (position — V_RIGHT_BOTTOM)

{

Line2D 1 = new Line2D. Double(xPosEnd, yPosMiddle,

XPosEnd, yPosBottom);

Gp. append(1, true);

}

}

DrawBar(V_RIGHT_BOTTOM, place);

Вызов drawBar в конце этого фрагмента кода присваивает переменной position значение одного из "перечислимых" типов. На основании этого присвоения выпол­няется та или иная ветвь метода drawBar. Преимущество этой системы перед цело­численными константами состоит в том, что она является безопасной в отношении типов.

Резюме

В этой главе я показал, как переместить компоненты, созданные в предыдущей главе, в их собственный каталог. Этот прием позволяет легко и просто задействовать свои файлы из нескольких проектов. Кроме того, он закладывает основу для следу­ющей главы, в которой вы узнаете, как упаковывать bean-компонент в JAR-файлы.

Вы также увидели весь существенный код, связанный с рисованием часов. Боль­шая часть этой задачи была сосредоточена на работе с Java2D.

В последних разделах главы был приведен еще один пример использования псевдо-перечислимого типа в Java-программе. Эта тема достаточно подробно рас­сматривалась в главе 16.

чарли калверт

Глава 37 _

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *