Каждый стандартный графический компонент — это прямоугольная область на экране со сторонами, параллельными сторонам экрана. Стороны прямоугольника выделяются каким-то образом на экране. Например, стороны кнопки JButton нарисованы так, что создают впечатление ее выпуклости. При нажатии кнопки мыши оформление сторон графической кнопки меняется, создавая впечатление ее "вдавленности".

Некоторые компоненты, например JLabei, вообще не оформляют свои границы.

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

Самые общие свойства всех рамок описаны интерфейсом Border из пакета javax. swing. border. Основное свойство — вычерчивание рамки методом

public void paintBorder(Component c, Graphics g,

int x, int y, int width, int height);

Здесь задается компонент c, который обводится рамкой, экземпляр g класса Graphics, обладающего методами рисования, и размеры рамки, которые обычно совпадают с размерами компонента, чуть больше или чуть меньше их.

Рамка может быть прозрачной или не прозрачной. Это отмечается логическим методом

isBorderOpaque().

Последний метод интерфейса, getBorderInsets(Component c), возвращает пространство, занятое рамкой данного компонента c, в виде экземпляра класса Insets. Напомним, что в классе Insets это пространство определяется толщиной рамки сверху top, слева left, справа right и снизу bottom. Все четыре поля класса Insets — просто целочисленные переменные, и получить толщину рамки сверху можно так:

int d = b.getBorderInsets(this).top;

Интерфейс Border частично реализован абстрактным классом AbstractBorder, в котором сделана пустая реализация метода paintBorder(), метод isBorderOpaque() возвращает false, а метод getBorderInsets () — объект с нулевыми значениями. Кроме этих реализаций в классе есть метод

public static Rectangle getInteriorRectangle(Component c, Border b,

int x, int y, int width, int height);

который удобно использовать для определения размеров самого компонента без рамки.

Класс AbstractBorder расширяют около двадцати классов, вычерчивающих самые разнообразные рамки. Для удобства работы с ними в пакете javax.swing имеется класс BorderFactory, в котором собраны статические методы вида createXxxBorder ( ) для различных типов рамок с разными параметрами. Чаще всего для создания рамки достаточно воспользоваться одним из этих методов, а затем установить полученную рамку в компонент методом setBorder(Border) класса JComponent. Например, на рис. 16.1 один из компонентов создан методами:

JLabel l2 = new JLabel(" LineBorder(Color.blue, 3) ");

l2.setBorder(BorderFactory.createLineBorder(Color.blue, 3));

Рассмотрим подробнее некоторые типы рамок. Простые типы рамок показаны на рис. 16.1, созданном программой листинга 16.1.

Листинг 16.1. Простые рамки

import java.awt.*; import javax.swing.*; import javax.swing.border.*;

public class SimpBorders extends JFrame{

SimpBorders(){

super(" Простые рамки"); setLayout(new FlowLayout());

JButton l1 = new JButton(" EmptyBorder() "); l1.setBackground(Color.white);

11. setBorder(BorderFactory.createEmptyBorder());

JLabel l2 = new JLabel(" LineBorder(Color.blue, 3) ");

12. setBorder(BorderFactory.createLineBorder(Color.blue, 3));

JLabel l3 = new JLabel(" BevelBorder(BevelBorder.RAISED) ");

13. setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));

JLabel l4 = new JLabel(" BevelBorder(BevelBorder.LOWERED) ");

14. setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));

JLabel l5 = new JLabel(" Объемная двухцветная рамка ");

15. setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED,

Color.black, Color.white, Color.black, Color.white));

JLabel l6 = new JLabel(" EtchedBorder() ");

l6.setBorder(BorderFactory.createEtchedBorder());

add(l1); add(l2); add(l3); add(l4); add(l5); add(l6);

setSize(400, 400);

setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);

public static void main(String[] args){ new SimpBorders();

}

}

Рис. 16.1. Простые рамки

Пустая рамка EmptyBorder

Класс EmptyBorder представляет самую простую рамку. Это пустое пространство, окружающее компонент, как показано на рис. 16.1, сверху слева. Конструкторы класса

EmptyBorder(Insets);

EmptyBorder(int top, int left, int bottom, int right);

задают толщину рамки.

Статический метод createEmptyBorder( ) класса BorderFactory создает пустую рамку с нулевыми размерами, а статический метод

createEmptyBorder(int top, int left, int bottom, int right);

рамку с заданными размерами.

Рамка невидима- метод isBorderOpaque() возвращает false, метод paintBorder() не вы

черчивает ничего. Метод getBorderInsets () без аргументов возвращает размеры рамки в виде экземпляра класса Insets.

Употребление пустой невидимой рамки сводится к определению экземпляра класса

EmptyBorder и установке его в компонент методом setBorder (Border) класса JComponent.

Прямолинейная рамка LineBorder

Класс LineBorder определяет одноцветную рамку заданной толщины, одинаковой на всех сторонах рамки. Она показана на рис. 16.1, сверху справа. Рамка заданного цвета толщиной в один пиксел создается конструктором LineBorder(Color). Конструктор LineBorder(Color, int) определяет вторым аргументом толщину линий. Конструктор LineBorder(Color, int, boolean), если третий аргумент равен true, создает рамку с закругленными краями.

Два статических метода, createBlackLineBorder() и createGrayLineBorder(), создают рамку с черными и серыми краями толщиной в один пиксел.

В классе BorderFactory есть два статических метода, аналогичные конструкторам класса

LineBorder: метод createLineBorder(Color) и метод createLineBorder(Color, int).

Объемная рамка BevelBorder

Рамка класса BevelBorder состоит из двух линий: светлой и темной. Если светлая линия расположена сверху и слева, а темная справа и снизу, то создается впечатление падения света сверху слева и компонент выглядит выпуклым. Это тип raised, он показан на рис. 16.1 во второй строке. Если же поменять местами темные и светлые линии, то компонент выглядит вдавленным в поверхность контейнера. Это тип LOWERED, на рис. 16.1 он показан в третьей строке. Именно так создается кнопка JButton.

Конструктор BevelBorder (int type) рисует рамку заданного типа type со светлыми линиями светлее фона контейнера и темными линиями темнее фона контейнера. Точно такие же рамки создаются статическими методами

createBevelBorder(int type); createRaisedBevelBorder(); createLoweredBevelBorder();

класса BorderFactory.

Конструктор

BevelBorder(int type, Color highlight, Color shadow);

или статический метод

createBevelBorder(int type, Color highlight, Color shadow);

класса BorderFactory создают рамку с заданным светлым highlight и темным shadow цветом.

Объемная рамка может состоять из двойных линий разных цветов. Конструктор

BevelBorder(int type, Color highlightOuter, Color highlightInner,

Color shadowOuter, Color shadowInner);

или статический метод

createBevelBorder(int type, Color highlightOuter, Color highlightInner,

Color shadowOuter, Color shadowInner);

создают объемную двухцветную рамку. Внутренние линии имеют цвета highlightInner и shadowInner, а внешние — цвета highlightOuter и shadowOuter.

Закругленная объемная рамка SoftBevelBorder

Класс SoftBevelBorder расширяет класс BevelBorder, создавая рамки со слегка закругленными, смягченными краями. Такие рамки создаются тремя конструкторами, аналогичными конструкторам класса BevelBorder:

SoftBevelBorder(int type);

SoftBevelBorder(int type, Color highlight, Color shadow);

SoftBevelBorder(int type, Color highlightOuter, Color highlightInner,

Color shadowOuter, Color shadowInner);

Врезанная рамка EtchedBorder

Рамка класса EtchedBorder похожа на объемную рамку, но имеет такие тонкие границы, что компонент с этой рамкой выглядит врезанным в контейнер, чуть-чуть выступая, если задана константа raised, или чуть-чуть вдавливаясь, если задана константа lowered. Такая рамка показана на рис. 16.1 в нижней строке справа. Она характерна для "приборного" стиля Java L&F, ранее называвшегося "Metal".

Стандартная врезанная рамка с цветами чуть светлее и чуть темнее цвета фона контейнера создается конструктором по умолчанию EtchedBorder () или статическим методом

createEtchedBorder() класса BorderFactory.

Тип рамки raised или lowered задается конструктором EtchedBorder (int) или статическим методом createEtchedBorder (int).

Цвета чуть выпуклой кнопки определяются конструктором

EtchedBorder(Color highlight, Color shadow);

или статическим методом

createEtchedBorder(Color highlight, Color shadow);

Наконец, можно задать и тип, и цвета конструктором

EtchedBorder(int type, Color highlight, Color shadow);

или статическим методом

createEtchedBorder(int type, Color highlight, Color shadow);

Рамка с изображением MatteBorder

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

Рамка с изображением создается конструктором MatteBorder(icon). При этом ширина рамки определяется величиной изображения.

Ширину рамки c изображением или цветом можно определить конструкторами

MatteBorder(Insets, Icon);

MatteBorder(Insets, Color);

MatteBorder(int top, int left, int bottom, int right, Icon);

MatteBorder(int top, int left, int bottom, int right, Color);

или статическими методами

createMatteBorder(int top, int left, int bottom, int right, Icon); createMatteBorder(int top, int left, int bottom, int right, Color);

класса BorderFactory.

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

Рис. 16.2. Рамка с изображениями и рамка с линиями разной толщины

Листинг 16.2. Рамки с изображениями и разной толщины I

import java.awt.*; import javax.swing.*; import javax.swing.border.*;

public class MatBorders extends JFrame{

MatBorders(){

super(" Рамки с изображениями и разной толщины"); setLayout(new FlowLayout());

JLabel l1 = new JLabel(" MatteBorder(Icon) ");

11. setBorder(new MatteBorder(new ImageIcon("about16.gif")));

JLabel l2 = new JLabel(" MatteBorder(3,6,3,6, Color.red) ");

12. setBorder(BorderFactory.createMatteBorder(3,6,3,6, Color.red));

add(l1); add(l2);

setSize(400, 400);

setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);

}

public static void main(String[] args){ new MatBorders();

}

}

Рамки с надписями TitledBorder

Класс TitledBorder позволяет создать рамку с надписью. В простейшем случае конструктор TitledBorder(String) или статический метод createTitledBorder(String) класса BorderFactory создает простую рамку толщиной в один пиксел, в которую слева сверху вставлена строка. Это показано на рис. 16.3, сверху.

Надпись можно вставить в рамку любого типа. Для этого используется конструктор

TitledBorder(Border, String);

или статический метод

createTitledBorder(Border, String);

На рис. 16.3 во второй строке надпись вставлена в рамку класса EtchedBorder.

Надпись можно вставить в верхнюю границу рамки, top, написать выше верхней границы, above_top, или ниже верхней границы, below_top. То же самое можно сделать снизу: bottom, above_bottom, below_bottom. Эти константы — параметр pos в конструкторах и методах, описанных далее.

По умолчанию надпись располагается слева, left, но ее можно расположить по центру, center, или справа, right. Эти константы — параметр just в конструкторах и методах, описанных далее.

Все константы, определяющие место надписи, перечислены в листинге 16.5.

Все восемнадцать возможностей реализуются конструктором

TitledBorder(Border, String, int just, int pos);

или статическим методом

createTitledBorder(Border, String, int just, int pos);

Некоторые из этих возможностей показаны на рис. 16.3.

Кроме различного расположения надписи, для нее можно задать шрифт конструктором

TitledBorder(Border, String, int just, int pos, Font);

или статическим методом

createTitledBorder(Border, String, int just, int pos, Font);

Наконец, кроме расположения и шрифта можно определить еще и цвет надписи конструкторомTitledBorder(Border, String, int just, int pos, Font, Color); или статическим методом класса BorderFactorycreateTitledBorder(Border, String, int just, int pos, Font, Color);
Рис. 16.3. Рамки с надписями

Листинг 16.3 содержит программу, создавшую рис. 16.3. В ней приведены все описанные возможности создания рамок с надписями.

Листинг 16.3. Рамки с надписями

import java.awt.*; import javax.swing.*; import javax.swing.border.*;

public class TitBorders extends JFrame{

TitBorders(){

super(" Рамки с надписями"); setLayout(new FlowLayout());

JLabel l1 = new JLabel(" TitledBorder(String) "); l1.setBorder(new TitledBorder("Надпись"));

JLabel l2 = new JLabel(

" TitledBorder(new EtchedBorder(),\"Надпись\") ");

12. setBorder(BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "Надпись"));

JLabel l3 = new JLabel(

" Расположение CENTER," +

" ABOVE_TOP

Шрифт ITALIC, 18 ");

13. setBorder(BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "Надпись", TitledBorder.CENTER, TitledBorder.ABOVE_TOP, new Font("Times New Roman", Font.ITALIC, 18)));

JLabel l4 =

new JLabel(" Расположение RIGHT, BELOW TOP ");

14. setBorder(BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "Надпись", TitledBorder.RIGHT, TitledBorder.BELOW_TOP, new Font("Times New Roman", Font.ITALIC, 18),

Color.red ));

JLabel l5 =

new JLabel(" Расположение CENTER, BOTTOM ");

15. setBorder(BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "РџРѕРґРїРёСЃСЊ", TitledBorder.CENTER, TitledBorder.BOTTOM, new Font("Times New Roman", Font.ITALIC, 18),

Color.red ));

add(l1); add(l2); add(l3); add(l4); add(l5); setSize(400, 400);

setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);

public static void main(String[] args){ new TitBorders();

}

}

Сдвоенные рамки CompoundBorder

Класс CompoundBorder создает рамку, состоящую из двух вложенных рамок любых типов. Это делается конструктором CompoundBorder(Border out, Border in) или статическим методом createCompoundBorder(Border out, Border in) класса BorderFactory.

Рис. 16.4. Сдвоенные рамки

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

Листинг 16.4. Сдвоенные рамки

import java.awt.*; import javax.swing.*; import javax.swing.border.*;

public class CompBorders extends JFrame{

CompBorders(){

super(" Сдвоенные рамки"); setLayout(new FlowLayout());

JLabel l1 = new JLabel(

" CompoundBorder(TitledBorder, TitledBorder) "); l1.setBorder(new CompoundBorder(

BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "Заголовок",

TitledBorder.CENTER, TitledBorder.ABOVE_TOP,

new Font("Times New Roman", Font.ITALIC|Font.BOLD, 20)),

BorderFactory.createTitledBorder(

BorderFactory.createEtchedBorder(), "РџРѕРґРїРёСЃСЊ",

TitledBorder.RIGHT, TitledBorder.BOTTOM,

new Font("Times New Roman", Font.ITALIC, 12),

Color.red)

));

JLabel l2 = new JLabel(

" CompoundBorder(BevelBorder.RAISED, BevelBorder.RAISED) ");

12. setBorder(new CompoundBorder(

BorderFactory.createBevelBorder(BevelBorder.RAISED),

BorderFactory.createBevelBorder(BevelBorder.RAISED)

));

JLabel l3 = new JLabel(

" CompoundBorder(BevelBorder.RAISED, BevelBorder.LOWERED) ");

13. setBorder(new CompoundBorder(

BorderFactory.createBevelBorder(BevelBorder.RAISED),

BorderFactory.createBevelBorder(BevelBorder.LOWERED)

));

add(l1); add(l2); add(l3); setSize(400, 400);

setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);

}

public static void main(String[] args){ new CompBorders();

}

}

Создание собственных рамок

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

Для ее создания можно расширить абстрактный класс AbstractBorder, определив хотя бы один конструктор и переопределив методы paintBorder () и getBorderinsets (). Если рамка не прозрачна, то надо переопределить метод isBorderOpaque( ) так, чтобы он возвращал

true.

Свою рамку можно создать, расширив какой-либо класс рамок. В листинге 16.5 приведен пример рамки, расширяющей класс TitledBorder, в заголовок которой можно вставить не надпись, а компонент класса JComponent.

Поскольку класс TitledBorder не является контейнером, его расширение PlaceBorder служит только для рисования границ рамки и для определения места заголовка. Для вставки компонента в заголовок класс PlaceBorder погружается в контейнер PlaceBorderPane, расширяющий JPanel. Компонент помещается в этот контейнер на место, определенное классом PlaceBorder. Все это описано в листинге 16.5 и показано на рис. 16.5.

Листинг 16.5. Рамка с заголовком, содержащим компонент

import java.awt.*; import javax.swing.*; import javax.swing.border.*;

interface BorderConstants{
static public final int DEFAULT POSITION = 0;
static public final int ABOVE TOP = 1;
static public final int TOP = 2;
static public final int BELOW_TOP = 3;
static public final int ABOVE BOTTOM = 4;
static public final int BOTTOM = 5;
static public final int BELOW BOTTOM = 6;
static public final int DEFAULT JUSTIFICATION = 0
static public final int LEFT = 1;
static public final int CENTER = 2;
static public final int RIGHT = 3;
static public final int LEADING = 4;
static public final int TRAILING = 5;
static public final int EDGE SPACING = 2;
static public final int TEXT SPACING = 2;
static public final int TEXT_INSET_ H = 5; }

public class CompTitledTest extends JFrame implements BorderConstants{ public CompTitledTest(){

super(" Рамка с компонентом");

JLabel lab = new JLabel(" PlaceBorder(JLabel) ", new ImageIcon("middle.gif"), JLabel.LEFT);

PlaceBorderPane pbp =

new PlaceBorderPane(new EtchedBorder(), lab, CENTER, TOP); add(pbp);

setSize(300, 300);

setDefaultCloseOperation(EXIT ON CLOSE); setVisible(true);

}

public static void main(String[] args){

new CompTitledTest();

}

}

class PlaceBorderPane extends JPanel implements BorderConstants{ protected JComponent comp; protected JPanel p; protected PlaceBorder border;

public PlaceBorderPane(){

this(new JLabel(,,3arolnoBOK"));

}

public PlaceBorderPane(JComponent c){ this(null, c, LEFT, TOP);

}

public PlaceBorderPane(Border b, JComponent c, int just, int pos){ super(); comp = c;

border = new PlaceBorder(b, c, just, pos);

setBorder(border);

setLayout(null);

add(comp);

p = new JPanel();

add(p);

}

public JPanel getContentPane(){ return p;

}

public void doLayout(){

Insets insets = getInsets();

Rectangle r = getBounds(); r.x = 0; r.y = 0;

comp.setBounds(border.getComponentRect(r,insets)); r.x += insets.left; r.y += insets.top;

r.width -= insets.left + insets.right; r.height -= insets.top + insets.bottom; p.setBounds(r);

}

class PlaceBorder extends TitledBorder{ public PlaceBorder(JComponent c){ this(null, c, LEFT, TOP);

}

public PlaceBorder(Border b){ this(b, null, LEFT, TOP);

}

public PlaceBorder(Border b, JComponent c){ this(b, c, LEFT, TOP);

}

public PlaceBorder(Border b, JComponent c, int just, int pos){ super(b, null, just, pos, null, null); if (b == null)

border = super.getBorder();

public void paintBorder(Component c, Graphics g,

int x, int y, int width, int height){ Rectangle r = new Rectangle(

x + EDGE_SPACING, y + EDGE_SPACING, width - (EDGE_SPACING * 2), height - (EDGE_SPACING * 2));

Insets bIns = (border != null) ?

border.getBorderInsets(c) : new Insets(0, 0, 0, 0);

Rectangle rect = new Rectangle(x, y, width, height);

Insets insets = getBorderInsets(c);

Rectangle compR = getComponentRect(rect, insets); int diff;

switch (titlePosition){

case ABOVE TOP:

diff = compR.height + TEXT SPACING; r.y += diff; r.height -= diff; break; case TOP:

case DEFAULT_POSITION:

diff = insets.top/2 — bIns.top — EDGE SPACING; r.y += diff; r.height -= diff; break;

case BELOW TOP: case ABOVE_BOTTOM: break;

case BOTTOM:

diff = insets.bottom/2 — bIns.bottom — EDGE SPACING;

r.height -= diff;

break;

case BELOW_BOTTOM:

diff = compR.height + TEXT SPACING; r.height -= diff;

}

border.paintBorder(c, g, r.x, r.y, r.width, r.height);

Color col = g.getColor(); g.setColor(c.getBackground());

g.fillRect(compR.x, compR.y, compR.width, compR.height); g.setColor(col); comp.repaint();

}

public Insets getBorderInsets(Component c, Insets insets){ Insets bIns = (border != null) ?

border.getBorderInsets(c) : new Insets(0, 0, 0, 0);

insets.top = EDGE_SPACING + TEXT_SPACING + bIns.top;

insets.right = EDGE_SPACING + TEXT_SPACING + bIns.right;

insets.bottom = EDGE_SPACING + TEXT_SPACING + bIns.bottom; insets.left = EDGE_SPACING + TEXT_SPACING + bIns.left;

if (c == null || comp == null) return insets;

int h = (comp != null) ? comp.getPreferredSize().height : 0;

switch (titlePosition){ case ABOVE TOP:

insets.top += h + TEXT_SPACING; break; case TOP:

case DEFAULT_POSITION:

insets.top += Math.max(h, bIns.top) — bIns.top; break;

case BELOW TOP:

insets.top += h + TEXT_SPACING; break;

case ABOVE_BOTTOM:

insets.bottom += h + TEXT_SPACING; break;

case BOTTOM:

insets.bottom += Math.max(h, bIns.bottom) — bIns.bottom; break;

case BELOW_BOTTOM:

insets.bottom += h + TEXT_SPACING;

}

return insets;

}

public Rectangle getComponentRect(Rectangle rect, Insets bIns){ Dimension d = comp.getPreferredSize();

Rectangle r = new Rectangle(0, 0, d.width, d.height); switch (titlePosition){ case ABOVE TOP:

r.y = EDGE_SPACING; break; case TOP:

case DEFAULT_POSITION: r.y = EDGE_SPACING +

(bIns.top — EDGE_SPACING — TEXT_SPACING — d.height)/2;

break;

case BELOW TOP:

r.y = bIns.top — d.height — TEXT SPACING; break;

case ABOVE_BOTTOM:

r.y = rect.height — bIns.bottom + TEXT SPACING; break;

case BOTTOM:

r.y = rect.height — bIns.bottom + TEXT SPACING +

(bIns.bottom — EDGE_SPACING — TEXT_SPACING — d.height)/2;

break;

case BELOW_BOTTOM:

r.y = rect.height — d.height — EDGE SPACING;

}

switch (titleJustification) { case LEFT:

case DEFAULTJUSTIFICATION:

r.x = TEXT_INSET_H + bIns.left; break; case RIGHT:

r.x = rect.width — bIns.right -TEXT INSET H — r.width; break;

case CENTER:

r.x = (rect.width — r.width) / 2;

}

return r;

}

Рис. 16.5. Рамка с компонентом JLabel

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

1. Какие компоненты Swing можно окружить рамкой?

2. Можно ли окружить рамкой контейнер?

3. Можно ли сделать несколько рамок для одного и того же компонента?

4. Можно ли поменять рамку при наступлении какого-нибудь события?

5. Можно ли поменять цвет рамки при наступлении какого-нибудь события?

6. Можно ли сделать рамку не для всех, а только для одной или нескольких сторон компонента?

7. Можно ли сделать разные рамки для разных сторон компонента?

8. Как меняется рамка при изменении размеров окна?

ГЛАВА 17