Этой статьей мы открываем новый цикл статей, посвященных библиотеке Java-классов под названием Swing.

Развитие языка Java идет в разных направлениях. Помимо "наращивания мускулов" в области коммуникаций, многопоточности и различных встроенных языковых и системных возможностей, JavaSoft пропагандирует переход к так называемым "легким" (lightweight) компонентам пользовательского интерфейса. В ранних - 1.0.x - версиях Java Development Kit активно использовались "тяжелые" компоненты AWT, довольно-таки плотно завязанные с аппаратными платформами. Дальнейшее развертывание концепции "write once, run everywhere" (написать однажды, запускать везде) привело к тому, что в версии 1.1.x наметился переход к таким компонентам, которые бы не были завязаны на конкретные "железо" и операционные системы. Вот здесь и появились те самые "легкие" интерфейсные классы, в которых любой компонент на экране создается средствами Java с помощью графических классов и методов. Такого рода классы компонентов были объединены в библиотеку под названием Swing и доступны разработчикам в составе как JDK, так и отдельного продукта JFC (Java Foundation Classes). Причем для совместимости со старыми версиями JDK старые компоненты из AWT остались нетронутыми, хотя компания JavaSoft - фирма, отвечающая за выпуск JDK, - рекомендует не смешивать в одной и той же программе старые и новые компоненты.

Одна из необходимейших вещей, встроенных в Swing, - система быстрой настройки интерфейса pluggabe Look and Feel (L&F). С ее помощью вы можете изменить внешний вид Java-приложения, написав всего несколько строчек. К примеру, одна и та же программа может выглядеть и как обычное Windows-приложение, и как Unix-приложение с интерфейсом Motif и т. д. Для кроссплатформного использования по умолчанию применяется интерфейс под названием Metal, напоминающий металлический лист с гравировкой.

Также важно знать, что многие компоненты Swing реализованы по технологии Model-View-Controller (MVC). Это означает, что компонент условно разбит на три части: модель (model), вид (view) и контроллер (controller). Каждая из этих частей отвечает за свой функциональный сектор. Модель хранит важные данные компонента и обеспечивает программный интерфейс к ним. Вид, как и следовало ожидать, занимается внешним видом изображения компонента и тесно связан с системой настройки интерфейса L&F. Контроллер же управляет компонентом в целом, получая сигналы от вида и уведомляя об изменениях модель компонента. Такое разграничение важно при смене L&F. Реализуя свой компонент для Swing, естественно будет сделать его пригодным для разных интерфейсов L&F. Технология MVC - это все, что вам нужно. Достаточно написать различные виды для Metal L&F, Motif L&F, Windows L&F и т. п. При этом модель и контроллер компонента меняются редко (почти что никогда).

Приступаем

Все примеры, приведенные в данной статье, выполнены с применением JDK 1.1.6 и библиотеки Swing 1.0.2. Оба этих продукта можно обнаружить на Web-сервере http://www.javasoft.com. Для компиляции применяется следующая команда:

javac Sample.java

Для корректного запуска подключения библиотеки Swing и запуска готовых программ создайте командный файл run.bat со следующим текстом:

java -classpath .;%CLASSPATH% Sample

Наша задача для начала - создать заготовку для будущих примеров. Ниже показан ее исходный текст (см. листинг 1).

Обратите внимание на директиву импорта

import com.sun.java.swing.*;

Следующий момент - использование классов JFrame и JPanel из библиотеки Swing вместо Frame и Panel из стандартного набора графических классов AWT. Обратите внимание: значительная часть имен компонентов в Swing начинаются с буквы J. Более того, панели класса JPanel должны добавляться в окна класса JFrame исключительно методом setContentPane(), а не add(), как это делается в AWT. Если вы все же попытаетесь использовать add(), то "поощрением" вам станет сообщение о неисправимой ошибке, которое будет выглядеть примерно так:

java.lang.Error: Do not use _Swing.add() use _Swing.getContentPane().add() instead
        at java.lang.Throwable.(Compiled Code)
        at com.sun.java.swing.JFrame.createRootPaneException(Compiled Code)
        at com.sun.java.swing.JFrame.addImpl(Compiled Code)
        at java.awt.Container.add(Compiled Code)
        at _Swing.(_Swing.java:39)
        at _Swing.main(_Swing.java:73)

Если в дальнейшем вы предполагаете активно использовать возможности системы настройки интерфейса L&F, то как нельзя кстати придутся две строки:

UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName());

Они демонстрируют, как специальный класс UIManager с помощью метода setLookAndFeel задает новый интерфейс, отличный от интерфейса, установленного L&F по умолчанию. На самом деле явно не задается конкретный интерфейс, а просто вызывается метод getCrossPlatformLookAndFeelClassName, возвращающий ссылку на тот интерфейс L&F, который является переносимым с платформы на платформу. По умолчанию таковым является Metal L&F.

Из исходных текстов метода main программы виден следующий процесс, в комментариях не нуждающийся: создается класс Sample, устанавливается класс-адаптер, отвечающий за закрытие окна программы, после чего окно изменяет размер и выводится на экран. Единственное, что способно затруднить понимание, так это абракадабра из символов UNICODE в строке, определяющей текст заголовка окна. Это не что иное, как слово "Библиотека". Чтобы получить цепочку UNICODE-текста, запустите утилиту native2ascii, входящую в комплект JDK, и наберите текст, требующий перекодировки. В ответ утилита напечатает на экране транскрипцию вашего текста на UNICODE, пригодную для применения в Java-программах. Для удобства можно воспользоваться перенаправлением потоков ввода и вывода. Наберите транслируемый текст и сохраните его как файл, присвоив ему имя, скажем, NATIVE:

native2ascii UNICODE

Текст будет считан из файла NATIVE, а результат перекодирования будет записан в файл UNICODE, откуда его легко скопировать через буфер.

Рамки

Библиотека Swing насчитывает несколько рамок, которые можно добавлять в окно приложения, чтобы его дополнительно украсить. В большинстве случаев для получения рамки вызывается специальный класс-фабрика BorderFactory. Заглянув в его исходные тексты, вы поймете, что вся его работа состоит в том, чтобы создать новый экземпляр рамки того или иного типа и вернуть ссылку на него. Некоторым дополнительным преимуществом при использовании класса BorderFactory можно считать возможность иметь объекты-рамки. При вызове некоторых методов класса BorderFactory (не описанных в текущем варианте документации, как, например, createSharedBorder) вашей программе будет возвращена ссылка на уже готовую рамку типа BevelBorder. При каждом новом обращении к createSharedBorder будет возвращаться одна и та же ссылка на готовую разделяемую рамку. Этим и объясняется название метода createSharedBorder. Работать с рамками с помощью класса BorderFactory довольно удобно. Единственным недостатком этого класса является то, что не всякая рамка может быть им изготовлена. К тому же методы класса BevelBorder статические, и при их вызове следует указывать и само имя класса.

Простые рамки

BevelBorder - это обычная для графического интерфейса рамка, имитирующая либо выпуклость, либо вогнутость панели. Чтобы получить рамку типа BevelBorder, нужно вызвать метод createBevelBorder класса BorderFactory (см. листинг 2. Приводятся только измененные участки программы).

В нашем примере мы используем самый простой метод createBevelBorder.

В других вариантах метода параметров больше и управляют они цветами элементов рамки.

Обратите внимание на параметр createBevelBorder, которым задается тип рельефа рамки. Константой BevelBorder.RAISED создается выпуклая рамка, тогда как с помощью константы BevelBorder.LOWERED изготавливается углубленная рамка.

Очень близкими к BevelBorder по дизайну являются рамки SoftBevelBorder и EtchedBorder. Первая из них должна отображать рамку, похожую на BevelBorder, но со "смягченными" краями. Однако визуально не удалось обнаружить никакой разницы между рамками SoftBevelBorder и BevelBorder. Тем не менее приводим пример создания рамки SoftBevelBorder (листинг 3) и обращаем ваше внимание на то, что в классе BorderFactory отсутствует специальный метод, которым бы можно было создать рамку SoftBevelBorder, поэтому в листинге показано создание рамки оператором new.

Рамка EtchedBorder имеет вид тонкого гравированного углубления или, наоборот, тонкого возвышения. Правда, с помощью класса BorderFactory можно создавать только углубленные рамки. Поэтому опять-таки если вам требуется большая степень контроля над создаваемыми объектами EtchedBorder, то создавайте их оператором new, хотя управление цветами элементов EtchedBorder вам доступно и в случае применения класса BorderFactory (см. листинг 4 ).

Сложные рамки

Интересный вариант сложной рамки - EmptyBorder. Эта прозрачная рамка не отображается на экране. Однако место в окне программы она все же занимает. Поначалу обрамление такого рода кажется странным. Но вы оцените EmptyBorder по достоинству, если потребуется более или менее точно позиционировать элементы пользовательского интерфейса в окне или создать зазор между группой элементов и краем окна.

В этом листинге панель настроена на использование менеджера размещения BorderLayout. Если бы в этом примере не использовалась прозрачная рамка, кнопки бы заняли все окно. Но этого не происходит благодаря наличию в программе объекта класса EmptyBorder. Отступ верхнего левого и нижнего правого углов рамки задаются параметрами, которые вы передаете методу createEmptyBorder класса BorderFactory.

Если вы стремитесь логически объединить элементы в окне или в панели, то для этой задачи подойдет рамка TitledBorder.

Листинг 6 демонстрирует, как создается TitledBorder, принимает массу параметров, что дает возможность настраивать вид ограничительной линии рамки, текст и его местоположение различными способами. Для простоты мы применили в качестве ограничителя рамку EtchedBorder, однако можно подставить и любой рамочный класс. Текст заголовка закодирован символами UNICODE и представляет собой слово "Заголовок". Местоположение текста определяется константами TitledBorder.BUTTOM и TitledBorder.RIGHT, что соответствует нижнему правому углу рамки. Остальные константы выравнивают текст по другим краям.

Следующая рамка - MatteBorder - заполняет все отведенное ей пространство графическим изображением, которое берется из файлов формата GIF или JPG.

Если изображение слишком велико для окна, оно будет усечено. Если же окно больше изображения, то все отведенное под рамку пространство будет заполнено по принципу мозаики, т. е. одна и та же картинка будет показана столько раз, сколько нужно для покрытия всей отведенной площади окна или панели. Листинг 7 демонстрирует создание объекта MatteBorder. Обратите внимание на параметры метода createMatteBorder, задающие размер рамки, и последний параметр, определяющий файл рисунка-заполнителя.

И еще одна рамка, технологии создания которой мы коснемся, - это CompoundBorder. Она служит для объединения двух заданных программистом рамок в одну. При этом сначала создается первая рамка, а уже внутри нее отображается вторая. Ничего сложного в этом нет. Вызывается метод createCompoundBorder, и ему передается наружная рамка в качестве первого параметра и внутренняя в качестве второго (см. листинг 8).


Листинг 1 Заготовка для примеров

import com.sun.java.swing.*;
import java.awt.event.*;
import com.sun.java.swing.border.*;
import java.awt.Color;

public class Sample extends JFrame
{
  private JPanel jp = new JPanel();

  public Sample(String caption)
  {
    super(caption);
    try
    {
      UIManager.setLookAndFeel(
         UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch(Exception e){e.printStackTrace();};
    setContentPane(jp);
  }

  public static void main(String[] args)
  {
    Sample s = new Sample();
    s.addWindowListener(new WindowAdapter()
      {
        public void windowClosing(WindowEvent e)
        {
          System.exit(0);
        }
      });
    s.setSize(400, 300);
    s.setVisible(true);
  }
}

Листинг 2 Пример создания рамки BevelBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBorder(BorderFactory.createBevelBorder(
      BevelBorder.RAISED,
      Color.blue,
      Color.green,
      Color.yellow,
      Color.black));
  setContentPane(jp);
}

Листинг 3 Пример создания рамки SoftBevelBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBorder(new SoftBevelBorder(SoftBevelBorder.RAISED));
  setContentPane(jp);
}

Листинг 4 Пример создания рамки EtchedBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBorder(BorderFactory.createEtchedBorder(
      Color.green, Color.blue));
  setContentPane(jp);
}

Листинг 5 Пример создания рамки EmptyBorder

private JPanel jp = new JPanel(new java.awt.BorderLayout());
  
  public Sample(String caption)
  {
    super(caption);
    try
    {
      UIManager.setLookAndFeel(
         UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch(Exception e){e.printStackTrace();};
    jp.setBorder(BorderFactory.createEmptyBorder(40,25,40,25));
    jp.add(new JButton(
),>Center>); jp.add(new JButton(),>South>); setContentPane(jp); }

Листинг 6 Пример создания рамки TitledBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBorder(BorderFactory.createTitledBorder(
      BorderFactory.createEtchedBorder(),
,
      TitledBorder.RIGHT,
      TitledBorder.BOTTOM,
      new java.awt.Font(, java.awt.Font.BOLD, 12),
      Color.blue));
  setContentPane(jp);
}

Листинг 7 Пример создания рамки MatteBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBorder(BorderFactory.createMatteBorder(
        15,15,100,100,
        new ImageIcon(<.hover.jpg>)));
  setContentPane(jp);
}

Пример создания рамки CompoundBorder

public Sample(String caption)
{
  super(caption);
  try
  {
    UIManager.setLookAndFeel(
       UIManager.getCrossPlatformLookAndFeelClassName());
  }
  catch(Exception e){e.printStackTrace();};
  jp.setBackground(Color.black);
  jp.setBorder(BorderFactory.createCompoundBorder(
    BorderFactory.createEmptyBorder(10,10,10,10),
    BorderFactory.createMatteBorder(
      25,25,25,25,
      new ImageIcon(<.icon.gif>))));
  setContentPane(jp);
}