Java ui на чем лучше делать. JavaFX: послушаем музычку

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

Я уже привел небольшой пример написания своего UI, но есть и другие варианты кастомизации интерфейса. Каждый отдельный J-компонент производит свою Lightweight-отрисовку при помощи метода paint(), который можно легко переопределить и изменить. Напрямую (не всегда, но чаще всего) его лучше не использовать (не буду вдаваться в подробности, так как это целая тема для отдельного топика). Для следующего примера используем метод paintComponent(). Рассмотрим как его можно применить поближе…

Начну с примера - текстовое поле с визуальным фидбэком при отсутствии содержимого:

JTextField field = new JTextField()
{
private boolean lostFocusOnce = false ;
private boolean incorrect = false ;

{
// Слушатели для обновления состояния проверки
addFocusListener (new FocusAdapter()
{
public void focusLost (FocusEvent e)
{
lostFocusOnce = true ;

repaint ();
}
});
addCaretListener (new CaretListener()
{
public void caretUpdate (CaretEvent e)
{
if (lostFocusOnce)
{
incorrect = getText ().trim ().equals ("" );
}
}
});
}

protected void paintComponent (Graphics g)
{
super.paintComponent (g);

// Расширенная отрисовка при некорректности данных
if (incorrect)
{
Graphics2D g2d = (Graphics2D) g;

// Включаем антиалиасинг для гладкой отрисовки
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

// Получаем отступы внутри поля
Insets insets;
if (getBorder () == null )
{
insets = new Insets (2, 2, 2, 2);
}
else
{
insets = getBorder ().getBorderInsets (this );
}

// Создаем фигуру в виде подчеркивания текста
GeneralPath gp = new GeneralPath (GeneralPath.WIND_EVEN_ODD);
gp.moveTo (insets.left, getHeight () - insets.bottom);
for (int i = 0; i < getWidth () - insets.right - insets.left; i += 3)
{
gp.lineTo (insets.left + i,
getHeight () - insets.bottom - ((i / 3) % 2 == 1 ? 2: 0));
}

// Отрисовываем её красным цветом
g2d.setPaint (Color.RED);
g2d.draw (gp);
}
}
};

Наличие содержимого перепроверяется при печати и потере фокуса полем. Переключившись на другой компонент мы увидим как отрисовывается наше дополнение к JTextField"у:

Полный код примера можно взять .

Таким образом можно расширить любой доступный компонент быстро переопределив и дополнив метод отрисовки, не прибегая к написанию отдельных громоздких UI или полноценных компонентов. Плюс данный пример можно достаточно легко вынести в отдельный класс и использовать как готовый элемент для своего интерфейса.
Еще одним плюсом данного способа является то, что Вы получаете независимый от текущего установленного приложению/компоненту LaF/UI - он будет работать всегда. Естественно, для некоторых специфичных UI может понадобиться немного иная отрисовка - поддерживать её или нет - Вам решать.

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

За основу берутся 8 изображений 16х16 - 4 состояния фона чекбокса и 4 состояния галки (5 на самом деле, но 5ое мы добавим програмно):

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

public static List BG_STATES = new ArrayList ();
public static List CHECK_STATES = new ArrayList ();

static
{
// Иконки состояния фона
for (int i = 1; i <= 4; i++)
{
BG_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/" + i + ".png" )));
}

// Дополнительное "пустое" состояние выделения

new BufferedImage (16, 16, BufferedImage.TYPE_INT_ARGB)));

// Состояния выделения
for (int i = 1; i <= 4; i++)
{
CHECK_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/c" + i + ".png" )));
}
}

private Map iconsCache = new HashMap ();

private synchronized void updateIcon ()
{
// Обновляем иконку чекбокса
final String key = bgIcon + "," + checkIcon;
if (iconsCache.containsKey (key))
{
// Необходимая иконка уже была ранее использована
setIcon (iconsCache.get (key));
}
else
{
// Создаем новую иконку совмещающую в себе фон и состояние поверх
BufferedImage b = new BufferedImage (BG_STATES.get (0).getIconWidth (),
BG_STATES.get (0).getIconHeight (), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = b.createGraphics ();
g2d.drawImage (BG_STATES.get (bgIcon).getImage (), 0, 0,
BG_STATES.get (bgIcon).getImageObserver ());
g2d.drawImage (CHECK_STATES.get (checkIcon).getImage (), 0, 0,
CHECK_STATES.get (checkIcon).getImageObserver ());
g2d.dispose ();

ImageIcon icon = new ImageIcon (b);
iconsCache.put (key, icon);
setIcon (icon);
}
}

Остается добавить несколько обработчиков переходов состояний и мы получим анимированный переход между ними:

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

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

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


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

Итак, думаю достаточно разговоров о графике - о ней более подробно я расскажу в будущих топиках, а сейчас приведу немного интересного материала, который я наработал за достаточно долгое время «общения» со Swing и Graphics2D.

DnD и GlassPane

Думаю первое - Вам всем более чем известно, как и проблемы с ним связанные. Насчет второго - вероятно Вы вскользь слышали о GlassPane или может даже видели это старинное изображение (которое до сих пор актуально, между прочем) об устройстве различных слоев стандартных фреймов. Что же тут такого и зачем я вспомнил об этом? И тем более, как связаны DnD и GlassPane спросите Вы? Вот именно о том, как их связать и что из этого может выйти я и хочу рассказать в этой главе.

Чтож, начнем по порядку - что мы знаем о DnD?
У некоторых Swing-компонентов есть готовые реализации для драга (JTree и JList к примеру) - для других можно достаточно легко дописать свою. Чтобы не бросаться словами на ветер - приведу небольшой пример DnD стринга из лэйбла:

JLabel label = new JLabel ("Небольшой текст для DnD" );
label.setTransferHandler (new TransferHandler()
{
public int getSourceActions (JComponent c)
{
return TransferHandler.COPY;
}

public boolean canImport (TransferSupport support)
{
return false ;
}

protected Transferable createTransferable (JComponent c)
{
return new StringSelection (((JLabel) c).getText ());
}
});
label.addMouseListener (new MouseAdapter()
{
public void mousePressed (MouseEvent e)
{
if (SwingUtilities.isLeftMouseButton (e))
{
JComponent c = (JComponent) e.getSource ();
TransferHandler handler = c.getTransferHandler ();
handler.exportAsDrag (c, e, TransferHandler.COPY);
}
}
});

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

Но что делать, если необходимо отследить последовательность действий пользователя при самом перетаскивании?
Для этого есть отдельная возможность повесить слушатель:

DragSourceAdapter dsa = new DragSourceAdapter()
{
public void dragEnter (DragSourceDragEvent dsde)
{
// При входе драга в область какого-либо компонента
}

public void dragExit (DragSourceEvent dse)
{
// При выходе драга в область какого-либо компонента
}

public void dropActionChanged (DragSourceDragEvent dsde)
{
// При смене действия драга
}

public void dragOver (DragSourceDragEvent dsde)
{
// При возможности корректного завершения драга
}

public void dragMouseMoved (DragSourceDragEvent dsde)
{
// При передвижении драга
}

public void dragDropEnd (DragSourceDropEvent dsde)
{
// При завершении или отмене драга
}
};
DragSource.getDefaultDragSource ().addDragSourceListener (dsa);
DragSource.getDefaultDragSource ().addDragSourceMotionListener (dsa);

Остался последний момент - обозначить роль GlassPane. GlassPane, фактически, позволяет располагать/отрисовывать на себе компоненты, как и любой другой контейнер, но его особенность в том, что он лежит поверх всех Swing-компонентов, когда виден. Т.е. если мы что-либо отрисуем на нем, то оно накроет весь находящийся под ним интерфейс. Это позволяет размещать компоненты независимо от основного контейнера в любом месте, создавать любые визуальные эффекты и делать другие занятные вещи.

Приведу для большего понимания небольшой пример подобного «эффекта» - фрейм с несколькими Swing-компонентами на нем. При клике в любой части окна будет появляться эффект «распозающегося» круга, который виден поверх всех элементов. Что самое интересное - подобный эффект не съедает ресурсов и не требует большой груды кода. Не верите? - посмотрите демо и загляните в исходник, вложенный в jar.

Кстати говоря, есть достаточно интересная библиотека на эту тему, заодно предоставляющая дополнительный скролл-функционал и несколько других вкусностей - JXLayer (офф сайт) (описание #1 описание #2 описание #3). К сожалению проекты хостящиеся на сайте java сейчас находтся не в лучшем состоянии, поэтому приходится ссылаться на отдельные ресурсы.

Итак теперь объединим всё что я уже описал в данной главе и сделаем, наконец, что-то полноценное. К примеру - отображение драга панели с компонентами внутри окна:

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

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

AWTUtilities

Так как уже достаточно давно в JDK6 включили некоторые будущие нововведения из 7ки, не могу обойти их стороной, так как с их помощью возможно много чего сделать приложив при этом гораздо меньшие усилия.
Итак, нас интересует несколько методов из AWTUtilities:
  1. AWTUtilities.setWindowShape(Window, Shape) - позволяет установить любому окну определенную форму (будь то круг или хитрый полигон). Для корректной установки формы окно не должно быть декорировано нативным стилем (setUndecorated(true)).
  2. AWTUtilities.setWindowOpacity (Window, float) – позволяет задать прозрачность окна от 0 (полностью прозрачно) до 1 (непрозрачно). Окно при этом может быть декорировано нативным стилем.
  3. AWTUtilities.setWindowOpaque (Window, boolean) – позволяет полностью спрятать отображение фона и оформления окна, но при этом любой размещенный на нем компонент будет виден. Для корректной установки данного параметра окно также как и в п.1 не должно быть декорировано нативным стилем.

Что же нам это дает? На самом деле - достаточно широкий спектр возможностей. Окнам своего приложения можно задавать любые хитрые формы какие Вам только потребуются, делать «дырки» посреди приложения, создавать кастомные тени под окно, делать приятные на вид попапы и пр. и пр.

Если переходить к конкретике - setWindowShape на деле я никогда не использую, так как задаваемая окну форма строго обрезается по краю и не очень приятно выглядит. На помощь приходит setWindowOpaque - спрятав оформление и фон окна можно с помощью контейнера с кастомным отрисованным фоном создавать абсолютно любые окна. Приведу небольшой пример использования (в нем также есть также использованы некоторые приемы из предыдущих глав поста):

можно взять работающий jar с исходным кодом. Честно скажу, что потратил на данный пример не более десяти минут (из них - минут пять прикидывал как расположить элементы внутри диалога:). Естественно это лишь один из вариантов примемения этих новых возможностей - на самом деле их куда больше.

Единственная неприятная мелочь в использовании AWTUtilities – нестабильная работа на Linux-системах. Т.е. Не везде и не всегда корректно отрабатывает прозрачность окон. Не уверен, проблемы ли это текущей JDK или же ОС.

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

Я уже поверхностно рассказал о том, как создавать компоненты, UI и некоторые «навороты» для интерфейса своего приложения, но что же делать, если нам нужно добавить функциональную часть к компоненту или создать свой совершенно новый компонент с некоей функциональностью и стилизацией? Стилизовать стандартные компоненты и делать из них отдельные части нового компонента достаточно долгое и нудное занятие, тем более что при малейшем изменении в одном из компонентов вся схема может поехать. Именно в таких случаях стоит сделать свой компонент «с нуля».

Итак, за основу лучше лучше всего взять JComponent и используя paint-методы отрисовать его содержимое. Фактически JСomponent сам по себе - чистый холст с некоторыми зашитыми улучшениями для отрисовками и готовыми стандартными методами setEnabled/setFont/setForeground/setBackground и т.п. Как использовать (и использовать ли их) решать Вам. Все, что Вы будете добавлять в методы отрисовки станет частью компонента и будет отображаться при добавлении его в какой-либо контейнер.

Кстати, небольшое отступление, раз уж зашла речь о контейнерах, - любой наследник и сам JComponent являются контейнерами, т.е. могут содержать в себе другие компоненты, которые будет расположены в зависимости от установленного компоненту лэйаута. Что же творится с отрисовкой чайлд-компонентов, лежащих в данном и как она связана с отрисовкой данного компонента? Ранее я не вдавался подробно в то, как устроены и связаны paint-методы Jcomponent"а, теперь же подробно опишу это…

Фактически, paint() метод содержит в себе вызовы 3ех отдельных методов - paintComponent, paintBorder и paintChildren. Конечно же дополнительно он обрабатывает некоторые «особенные» случаи отрисовки как, например печать или перерисовку отдельной области. Эти три метода всегда вызываются в показанной на изображении выше последовательности. Таким образом сперва идет отрисовка самого компонента, затем поверх рисуется бордер и далее вызывается отрисовка чайлд-компонентов, которые в свою очередь также вызывают свой paint() метод и т.д. Естественно есть еще и различные оптимизации, предотвращающие лишние отрисовки, но об этом подробнее я напишу потом.

Компонент отрисован, но статичен и представляет собой лишь изображение. Нам необходимо обработать возможность управления им мышью и различными хоткеями.
Для этого, во-первых, необходимо добавить соответствующие слушатели (MouseListener/MouseMotionListener/KeyListener) к самому компоненту и обрабатывать отдельные действия.

Чтобы не объяснять все на пальцах, приведу пример компонента, позволяющего визуально ресайзить переданный ему ImageIcon:

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

При создании данного компонента я бы выделил несколько важных моментов:

  1. Определяемся с функционалом и внешним видом компонента - в данном случае это область с размещенным на ней изображением, бордером вокруг изображения и 4мя ресайзерами по углам. Каждый из ресайзеров позволяет менять размер изображения. Также есть возможность передвигать изображение по области, «схватив» его за центр.
  2. Определяем все необходимые для работы компонента параметры - в данном случае это само изображение и его «опорные» точки (верхний левый и правый нижний углы). Также есть ряд переменных, которые понадобятся при реализации ресайза и драга изображения.
  3. Накидываем заготовку для компонента (желательно отдельный класс, если Вы собираетесь его использовать более раза) - в данном случае создаем класс ImageResizeComponent, определяем все необходимые для отрисовки параметры, переопределяем метод paintComponent() и отрисовываем содержимое. Также переопределяем метод getPreferredSize(), чтобы компонент сам мог определить свой «желаемый» размер.
  4. Реализовываем функциональную часть компонента - в данном случае нам будет достаточно своего MouseAdapter"а для реализации ресайза и передвижения. При нажатии мыши в области проверяем координаты и сверяем имх с координатами углов и самого изображения - если нажатие произошло в районе некого угла - запоминаем его и при драге изменяем его координату, ежели нажатие пришлось на изображение - запоминаем начальные его координаты и при перетаскивании меняем их. И наконец, последний штрих - в mouseMoved() меняем курсор в зависимости от контрола под мышью.
Ничего сложного, правда? С реализацией «кнопочных» частей других компонентов всё еще проще - достаточно проверять, что нажатие пришлось в область кнопки. Параллельно с отслеживанием событий можно также визуально менять отображение компонента (как сделано в данном примере на ресайзерах). В общем сделать можно всё, на что хватит фантазии.

Конечно, я просто описал свои шаги при создании компонентов - они не являются чем-то обязательным и могут быть легко расширены и дополнены.

Важно помнить

Есть несколько вещей, которые стоит при любой работе с итерфейсными элементами в Swing – приведу их в этой отдельной небольшой главе.
  1. Любые операции вызова перерисовки/обновления компонентов должны производится внутри Event Dispatch потока для избежания визуальных «подвисаний» интерфейса Вашего приложения. Выполнить любой код в Event Dispatch потоке достаточно легко:
    SwingUtilities.invokeLater (new Runnable()
    {
    public void run ()
    {
    // Здесь располагаете исполняемый код
    }
    });
    Важно также помнить, что события, вызываемые из различных listener"ов стандартных компонентов (ActionListener/MouseListener и пр.) исходно вызываются из этого потока и не требуют дополнительной обработки.
  2. Из любых paint-методов ни в коем случае нельзя влиять на интерфейс, так как это может привести к зацикливаниям или дедлокам в приложении.
  3. Представьте ситуацию - из метода paint Вы изменяете состояние кнопки, скажем, через setEnabled(enabled). Вызов этого метода заставляет компонент перерисоваться и заново вызвать метод paint и мы опять попадаем на этот злополучный метод. Отрисовка кнопки зациклиться и интерфейс приложения будет попросту висеть в ожидании завершения отрисовки (или как минимум съест добрую часть процессора). Это самый простой вариант подобной ошибки.
  4. Не стоит производить тяжёлые вычисления внутри Event Dispatch потока. Вы можете произвести эти вычисления в отдельном обычном потоке и затем только вызвать обновление через SwingUtilities.invokeLater().
  5. Не стоит, также, производить тяжёлые вычисления внутри методов отрисовки. По возможности стоит выносить их отдельно и кэшировать/запоминать. Это позволит ускорить отрисовку компонентов, улучшить общую отзывчиввость приложения и снизить нагрузку на Event Dispatch поток.
  6. Для обвноления внешнего вида компонентов используйте метод repaint() (или же repaint(Rectangle) – если Вам известна точная область для обновления), сам метод repaint необходимо исполнять в Event Dispatch потоке. Для обновления же расположения элементов в лэйауте используйте метод revalidate() (его нет необходимости исполнять в Event Dispatch потоке). Метод updateUI() может помочь в некоторых случаях для полного обновления элемента (например смене данных таблицы), но его нужно использовать аккуратно, так как он также отменит установленный Вами UI и возьмет UI предоставляемый текущим LaF"ом.
  7. Полная установка LaF всему приложению отменит любые ваши вручную заданные в ходе работы UI компонентов и установит поверх них те UI, которые предлагает устанавливаемый LaF. Поэтому лучше производить установку LaF при загрузке приложения до открытия каких-либо окон и показа визуальных элементов.
Следовние этим простым пунктам позволит Вам не беспокоиться о возникновении непредвиденных «тормозов» или дедлоков в интерфейсе приложения.
Думаю, что этот список можно дополнить еще несколькими пунктами, но они уже будут весьма специфичны/необязательны.

Итоги

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

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

Все примеры статьи в едином «флаконе». Из начального окна можно выбрать желаемый пример:

  • interface
  • dnd
  • customization
  • интерфейс
  • кастомизация
  • Добавить метки

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

    Java AWT

    Первой попыткой Sun создать графический интерфейс для Java была библиотека AWT (Abstract Window Toolkit) - инструментарий для работы с различными оконными средами. Sun сделал прослойку на Java, которая вызывает методы из библиотек, написанных на С. Библиотечные методы AWT создают и используют графические компоненты операционной среды. С одной стороны, это хорошо, так как программа на Java похожа на остальные программы в рамках одной ОС. Но при запуске ее на другой платформе могут возникнуть различия в размерах компонентов и шрифтов, которые будут портить внешний вид программы.

    Чтобы обеспечить мультиплатформенность AWT интерфейсы вызовов компонентов были унифицированы, вследствии чего их функциональность получилась немного урезанной. Да и набор компонентов получился довольно небольшой. Так например, в AWT нет таблиц, а в кнопках не поддерживается отображение иконок. Тем не менее пакет java.awt входит в Java с самого первого выпуска и его можно использовать для создания графических интерфейсов.

    Таким образом, компоненты AWT не выполняют никакой "работы". Это просто «Java-оболочка» для элементов управления той операционной системы, на которой они работают. Все запросы к этим компонентам перенаправляются к операционной системе, которая и выполняет всю работу.

    Использованные ресурсы AWT старается освобождать автоматически. Это немного усложняет архитектуру и влияет на производительность. Написать что-то серьезное с использованием AWT будет несколько затруднительно. Сейчас ее используют разве что для апплетов.

    Основные концепции SWING

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

    Создатели новой библиотеки пользовательского интерфейса Swing не стали «изобретать велосипед» и в качестве основы для своей библиотеки выбрали AWT. Конечно, речь не шла об использовании конкретных тяжеловесных компонентов AWT (представленных классами Button, Label и им подобными). Нужную степень гибкости и управляемости обеспечивали только легковесные компоненты. На диаграмме наследования представлена связь между AWT и Swing.

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

    Swing контейнеры высшего уровня

    Для создания графического интерфейса приложения необходимо использовать специальные компоненты библиотеки Swing, называемые контейнерами высшего уровня (top level containers). Они представляют собой окна операционной системы, в которых размещаются компоненты пользовательского интерфейса. К контейнерам высшего уровня относятся окна JFrame и JWindow, диалоговое окно JDialog, а также апплет JApplet (который не является окном, но тоже предназначен для вывода интерфейса в браузере, запускающем этот апплет). Контейнеры высшего уровня Swing представляют собой тяжеловесные компоненты и являются исключением из общего правила. Все остальные компоненты Swing являются легковесными.

    Простой Swing пример создания оконного интерфейса JFrame .

    Import java.awt.Dimension; import javax.swing.JFrame; import javax.swing.JLabel; public class JFrameTest { public static void createGUI() { JFrame frame = new JFrame("Test frame"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JLabel label = new JLabel("Test label"); frame.getContentPane().add(label); frame.setPreferredSize(new Dimension(200, 100)); frame.pack(); frame.setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createGUI(); } }); } }

    Конструктор JFrame() без параметров создает пустое окно. Конструктор JFrame(String title) создает пустое окно с заголовком title. Чтобы создать простейшую программу с пустым окном необходимо использовать следующие методы:

    • setSize(int width, int height) - определение размеров окна;
    • setDefaultCloseOperation(int operation) - определение действия при завершении программы;
    • setVisible(boolean visible) - сделать окно видимым.

    Если не определить размеры окна, то оно будет иметь нулевую высоту независимо от того, что в нем находится. Размеры окна включают не только «рабочую» область, но и границы и строку заголовка.

    Метод setDefaultCloseOperation определяет действие, которое необходимо выполнить при "выходе из программы". Для этого следует в качестве параметра operation передать константу EXIT_ON_CLOSE, описанную в классе JFrame.

    По умолчанию окно создается невидимым. Чтобы отобразить окно на экране вызывается метод setVisible с параметром true. Если вызвать его с параметром false, окно станет невидимым.

    Графический интерфейс java swing примера создания окна JFrame представлен на следующем рисунке.

    Для подключения библиотеки Swing в приложении необходимо импортировать библиотеку javax.swing .

    Каждый раз, как только создается контейнер высшего уровня, будь то обычное окно, диалоговое окно или апплет, в конструкторе этого контейнера создается корневая панель JRootPane . Контейнеры высшего уровня Swing следят за тем, чтобы другие компоненты не смогли "пробраться" за пределы JRootPane.

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

    На следующем рисунке наглядно представлена структура корневой панели JRootPane .

    Корневая панель JRootPane представляет собой контейнер, унаследованный от базового класса Swing JComponent. В этом контейнере за расположение компонентов отвечает специальный менеджер расположения, реализованный во внутреннем классе RootPaneLayout. Этот менеджер расположения отвечает за то, чтобы все составные части корневой панели размещались так, как им следует: многослойная панель занимает все пространство окна; в ее слое FRAME_CONTENT_LAYER располагаются строка меню и панель содержимого, а над всем этим располагется прозрачная панель.

    Все составляющие корневой панели JRootPane можно получить или изменить. Для этого у нее есть набор методов get/set. Программным способом JRootPane можно получить с использованием метода getRootPane().

    Кроме контейнеров высшего уровня корневая панель применяется во внутренних окнах JInternalFrame, создаваемых в многодокументных приложениях и располагающихся на "рабочем столе" JDesktopPane. Это позволяет забыть про то, что данные окна представляют собой обычные легковесные компоненты, и работать с ними как с настоящими контейнерами высшего уровня.

    Многослойная панель JLayeredPane

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

    JLayeredPane используется для добавления в контейнер свойства глубины (depth). To есть, многослойная панель позволяет организовать в контейнере третье измерение, вдоль которого располагаются слои (layers) компонента. В обычном контейнере расположение компонента определяется прямоугольником, который показывает, какую часть контейнера занимает компонент. При добавлении компонента в многослойную панель необходимо указать не только прямоугольник, занимаемый компонентом, но и слой, в котором он будет располагаться. Слой в многослойной панели определяется целым числом. Чем больше определяющее слой число, тем выше слой находится.

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

    Возможности многослойной панели широко используются некоторыми компонентами Swing . Особенно они важны для многодокументных приложений, всплывающих подсказок и меню. Многодокументные Swing приложения задействуют специальный контейнер JDesktopPane («рабочий стол»), унаследованный от JLayeredPane , в котором располагаются внутренние окна Swing. Самые важные функции многодокументного приложения - расположение «активного» окна над другими, сворачивание окон, их перетаскивание - обеспечиваются механизмами многослойной панели. Основное преимущество от использования многослойной панели для всплывающих подсказок и меню - это ускорение их работы. Вместо создания для каждой подсказки или меню нового тяжеловесного окна, располагающегося над компонентом, в котором возник запрос на вывод подсказки или меню, Swing создает быстрый легковесный компонент. Этот компонент размещается в достаточно высоком слое многослойной панели выше в стопке всех остальных компонентов и используется для вывода подсказки или меню.

    Многослойная панель позволяет организовать неограниченное количество слоев. Структура JLayeredPane включает несколько стандартных слоев, которые и используются всеми компонентами Swing, что позволяет обеспечить правильную работу всех механизмов многослойной панели. Стандартные слои JLayeredPane представлены на следующем рисунке.

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

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

    Слой Modal планировался для размещения легковесных модальных диалоговых окон. Однако такие диалоговые окна пока не реализованы, так что этот слой в Swing в настоящее время не используется.

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

    Самый верхний слой. Предназначен для операций перетаскивания (drag and drop), которые должны быть хорошо видны в интерфейсе программы.

    Небольшой пример JLayeredPane с многослойной панелью показывает, как добавлять компоненты в различные слои и как слои располагаются друг над другом:

    Import javax.swing.*; import java.awt.*; // класс рисования двух типов фигур с текстом class Figure extends JComponent { private static final long serialVersionUID = 1L; private Color color; private int type; private String text; // параметры: цвет и тип фигуры Figure(Color color, int type, String text) { this.color = color; this.type = type; this.text = text; setOpaque(false); } public void paintComponent(Graphics g) { // прорисовка фигуры g.setColor(color); switch (type) { case 0: g.fillOval(0, 0, 90, 90); break; case 1: g.fillRect(0, 0, 130, 80); break; } g.setColor(Color.yellow); g.drawString(text, 10, 35); } } public class JLayeredPaneTest extends JFrame { private static final long serialVersionUID = 1L; public JLayeredPaneTest() { // создание окна super("Example LayeredTest"); // выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE); // определение многослойной панели JLayeredPane lp = getLayeredPane(); // создание трех фигур Figure figure1 = new Figure(Color.red , 0, "Figure popup"); Figure figure2 = new Figure(Color.blue, 0, "Figure 1"); Figure figure3 = new Figure(Color.cyan, 1, "Figure 2"); // определение местоположения фигур в окне figure1.setBounds(10, 40, 120, 120); figure2.setBounds(60, 120, 160, 180); figure3.setBounds(90, 55, 250, 180); // добавление фигур в различные слои lp.add(figure1, JLayeredPane.POPUP_LAYER); lp.add(figure2, JLayeredPane.PALETTE_LAYER); lp.add(figure3, JLayeredPane.PALETTE_LAYER); // смена позиции одной из фигур lp.setPosition(figure3, 0); // определение размера и открытие окна setSize(280, 250); setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); new JLayeredPaneTest(); } }

    В примере создается небольшое окно JFrame и в многослойную панель добавляется несколько компонентов Figure. Чтобы получить многослойную панель в любом контейнере Swing высшего уровня, достаточно вызвать метод getLayeredPane() .

    Вспомогательный класс Figure наследует свойства базового класса JComponent и позволяет различными цветами рисовать фигуры двух типов (круги и прямоугольники). Параметры для прорисовки фигур задаются в конструкторе класса.

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

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

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

    Панель содержимого ContentPane

    Панель содержимого ContentPane - это следующая часть корневой панели, которая используется для размещения компонентов пользовательского интерфейса программы. ContentPane занимает большую часть пространства многослойной панели (за исключением места, занимаемого строкой меню). Чтобы панель содержимого не закрывала добавляемые впоследствии в окно компоненты, многослойная панель размещает ее в специальном очень низком слое с названием FRAME_CONTENT_LAYER, с номером -30000.

    Обратиться к панели содержимого можно методом getContentPane() класса JFrame. С помощью метода add(Component component) можно добавить на нее любой элемент управления. Заменить ContentPane любой другой панелью типа JPanel можно методом setContentPane()

    Пример добавления кнопки в панель содержимого:

    JButton newButton = new JButton(); getContentPane().add(newButton);

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

    Панель содержимого можно полностью заменить. Рассмотрим следующий Swing пример использования панели содержимого ContentPane .

    Import javax.swing.*; public class ContentPaneReplace extends JFrame { private static final long serialVersionUID = 1L; public ContentPaneReplace() { super("Test ContentPane"); setDefaultCloseOperation(EXIT_ON_CLOSE); // Создание панели с двумя кнопками JPanel contents = new JPanel(); contents.add(new JButton("Семья")); contents.add(new JButton("Школа")); // Замена панели содержимого setContentPane(contents); // Определение размера окна setSize(200, 100); // Открытие окна setVisible(true); } public static void main(String args) { JFrame.setDefaultLookAndFeelDecorated(true); new ContentPaneAdd(); } }

    В примере создается небольшое окно и панель с двумя кнопками, которая затем методом setContentPane() заменяет панель содержимого окна. Таким образом была использована замена вместо более простого добавления - вызова метода add(). Интерфейс окна представлен на следующем скриншоте.

    Панель содержимого ContentPane сама собой не представляет ничего особенного. Необходимо лишь помнить, что компоненты добавляются именно в нее.

    Прозрачная панель JOptionPane

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

    JOptionPane используется в приложениях достаточно редко, поэтому по умолчанию корневая панель делает ее невидимой, что позволяет уменьшить нагрузку на систему рисования. Следует иметь в виду, что если вы делаете прозрачную панель видимой, нужно быть уверенным в том, что она прозрачна (ее свойство opaque равно false), поскольку в противном случае она закроет все остальные элементы корневой панели, и остальной интерфейс будет невидим.

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

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

    Пример использования прозрачной панели Swing JOptionPane:

    // Использование прозрачной панели JOptionPane import java.awt.Dimension; import java.awt.Font; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.UIManager; public class JOptionPaneTest extends JFrame { private static final long serialVersionUID = 1L; public static final Font FONT = new Font("Verdana", Font.PLAIN, 11); public static void createGUI() { JFrame frame = new JFrame("Test JOptionPane"); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowListener() { public void windowActivated(WindowEvent event) {} public void windowClosed(WindowEvent event) {} public void windowDeactivated(WindowEvent event) {} public void windowDeiconified(WindowEvent event) {} public void windowIconified(WindowEvent event) {} public void windowOpened(WindowEvent event) {} public void windowClosing(WindowEvent event) { Object options = { "Да", "Нет!" }; int rc = JOptionPane.showOptionDialog(event.getWindow(), "Закрыть окно?", "Подтверждение", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options); if (rc == 0) { event.getWindow().setVisible(false); System.exit(0); } } }); JLabel label = new JLabel("Использование прозрачной панели при закрытии окна"); frame.getContentPane().add(label); frame.setPreferredSize(new Dimension(350, 80)); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { UIManager.put("Button.font", FONT); UIManager.put("Label.font", FONT); JFrame.setDefaultLookAndFeelDecorated(true); JDialog.setDefaultLookAndFeelDecorated(true); createGUI(); } }); } }

    Если методу setDefaultCloseOperation передать константу JFrame.EXIT_ON_CLOSE , то при закрытии окна приложение будет прекращать работу. В примере этому методу передается константа JFrame.DO_NOTHING_ON_CLOSE , чтобы при закрытии окна ничего не происходило. Выход из приложения в примере осуществляется из JFrame слушателя WindowListener в методе windowClosing . При закрытии окна вызывается метод windowClosing с параметром WindowEvent event, который в прозрачной панели Swing JOptionPane открывает диалоговое окно подтверждения.

    На следующем скриншоте представлены два окна приложения. Верхнее главное окно. При закрытии данного окна открывается нижнее диалоговое окно подтверждения намерения.

    Строка меню JMenuBar

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

    Строка меню JMenuBar размещается в многослойной панели в специальном слое FRAME_CONTENT_LAYER и занимает небольшое пространство в верхней части окна. По размерам в длину строка меню равна размеру окна. Ширина строки меню зависит от содержащихся в ней компонентов.

    Корневая панель следит, чтобы панель содержимого и строка меню JMenuBar не перекрывались. Если строка меню не требуется, то корневая панель использует все пространство для размещения панели содержимого.

    Примеры Swing

    Исходные коды примеров, рассмотренных в тексте страницы, можно скачать .

    Библиотека Swing

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

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

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

    Каждая библиотека предоставляет набор классов для работы с кнопками, списками, окнами, меню и т.д., но эти классы спроектированы по-разному: они имеют различный набор методов с разными параметрами, поэтому «перевести» программу с одной библиотеки на другую (например, с целью увеличения быстродействия) не так-то просто. Это почти как перейти с одного языка программирования на другой: все языки умеют делать одно и то же, но у каждого из них свой синтаксис, своя программная структура и свои многочисленные хитрости.

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

    Окно JFrame

    Каждая GUI-программа запускается в окне и по ходу работы может открывать несколько дополнительных окон.

    В библиотеке Swing описан класс JFrame , представляющий собой окно с рамкой и строкой заголовка (с кнопками «Свернуть», «Во весь экран» и «Закрыть»). Оно может изменять размеры и перемещаться по экрану.

    об окнах Swing

    В Swing есть еще несколько классов окон. Например, JWindow - простейшее окно, без рамки и без строки заголовка. Обычно с его помощью делается заставка к программе, которая перед запуском должна выполнить несколько продолжительных действий (например, загрузить информацию из БД).

    Конструктор JFrame() без параметров создает пустое окно. Конструктор JFrame(String title) создает пустое окно с заголовком title .

    Чтобы написать простейшую программу, выводящую на экран пустое окно, нам потребуется еще три метода:

    setSize(int width, int height) - устанавливает размеры окна. Если не задать размеры, окно будет иметь нулевую высоту независимо от того, что в нем находится и пользователю после запуска придется растягивать окно вручную. Размеры окна включают не только «рабочую» область, но и границы и строку заголовка.

    setDefaultCloseOperation(int operation) - позволяет указать действие, которое необходимо выполнить, когда пользователь закрывает окно нажатием на крестик. Обычно в программе есть одно или несколько окон при закрытии которых программа прекращает работу. Для того, чтобы запрограммировать это поведение, следует в качестве параметра operation передать константу EXIT_ON_CLOSE , описанную в классе JFrame .

    setVisible(boolean visible) - когда окно создается, оно по умолчанию невидимо. Чтобы отобразить окно на экране, вызывается данный метод с параметром true . Если вызвать его с параметром false , окно снова станет невидимым.

    Теперь мы можем написать программу, которая создает окно, выводит его на экран и завершает работу после того, как пользователь закрывает окно.

    import javax.swing.*; public class MyClass { public static void main (String args) { JFrame myWindow = new JFrame("Пробное окно" ); myWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); myWindow.setSize(400, 300); myWindow.setVisible(true ); } }

    Обратите внимание, для работы с большинством классов библиотеки Swing понадобится импортировать пакет java.swing.*

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

    В файле SimpleWindow.java:

    public class SimpleWindow extends JFrame { SimpleWindow(){ super ("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(250, 100); } }

    В файле Program.java:

    public class Program { public static void main (String args) { JFrame myWindow = new SimpleWindow(); myWindow.setVisible(true ); } }

    Из примера видно, что окно описывается в отдельном классе, являющемся наследником JFrame и настраивающее свой внешний вид и поведение в конструкторе (первой командой вызывается конструктор суперкласса). Метод main() содержится в другом классе, ответственном за управление ходом программы. Каждый из этих классов очень прост, каждый занимается своим делом, поэтому в них легко разбираться и легко сопровождать (т.е. совершенствовать при необходимости).

    Обратите внимание, что метод setVisible() не вызывается в классе SimpleWindow , что вполне логично: за тем, где какая кнопка расположена и какие размеры оно должно иметь, следит само окно, а вот принимать решение о том, какое окно в какой момент выводится на экран - прерогатива управляющего класса программы.

    Панель содержимого

    Напрямую в окне элементы управления не размещаются. Для этого служит панель содержимого, занимающая все пространство окна* . Обратиться к этой панели можно методом getContentPane() класса JFrame . С помощью метода add(Component component) можно добавить на нее любой элемент управления.

    В примерах этого занятия мы будем использовать только один элемент управления - кнопку (не вдаваясь в подробности ее устройства). Кнопка описывается классом JButton и создается конструктором с параметром типа String - надписью.

    Добавим кнопку в панель содержимого нашего окна командами:

    JButton newButton = new JButton(); getContentPane().add(newButton);

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

    Класс Container (контейнер)

    Элементы, которые содержат другие элементы, называются контейнерами. Все они являются потомками класса Container и наследуют от него ряд полезных методов:

    add(Component component) - добавляет в контейнер элемент component ;

    remove(Component component) - удаляет из контейнера элемент component ;

    removeAll() - удаляет все элементы контейнера;

    getComponentCount() - возвращает число элементов контейнера.

    Кроме перечисленных в классе Container определено около двух десятков методов для управления набором компонентов, содержащихся в контейнере. Как видно, они похожи на методы класса-коллекции. Это неудивительно, ведь по сути контейнер и является коллекцией, но коллекцией особого рода - визуальной. Кроме хранения элементов контейнер занимается их пространственным расположением и прорисовкой. В частности, он имеет метод getComponentAt(int x, int y) , возвращающий компонент, в который попадает точка с заданными координатами (координаты отсчитываются от левого верхнего угла компонента) и ряд других. Мы не будем подробно рассматривать абстрактный контейнер, а сразу перейдем к его наиболее часто используемому потомку - классу JPanel .

    Класс JPanel (панель)

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

    В примере с кнопкой мы наблюдали, как добавленная на панель содержимого кнопка заняла все ее пространство. Это происходит не всегда. На самом деле у каждой панели есть так называемый менеджер размещения , который определяет стратегию взаимного расположения элементов, добавляемых на панель. Его можно изменить методом setLayout(LayoutManager manager) . Но чтобы передать в этот метод нужный параметр, необходимо знать, какими бывают менеджеры.

    Менеджер последовательного размещения FlowLayout

    Самый простой менеджер размещения - FlowLayout . Он размещает добавляемые на панель компоненты строго по очереди, строка за строкой, в зависимости от размеров панели. Как только очередной элемент не помещается в текущей строке, он переносится на следующую. Лучше всего пронаблюдать это на примере. Изменим конструктор класса SimpleWindow следующим образом:

    SimpleWindow(){ super ("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new FlowLayout()); panel.add(new JButton("Кнопка" )); panel.add(new JButton("+" )); panel.add(new JButton("-" )); panel.add(new JButton("Кнопка с длинной надписью" )); setContentPane(panel); setSize(250, 100); }

    Менеджеры расположения описаны в пакете java.awt. Не забывайте импортировать нужные классы.

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

    Проанализируем текст примера. Новый менеджер расположения FlowLayout создается конструктором без параметров. Обратите внимание, в программе не используется промежуточная переменная. То есть вместо двух команд:

    FlowLayout newLayout = new FlowLayout(); panel.setLayout(newLayout);

    Мы используем одну:

    Panel.setLayout(new FlowLayout());

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

    о взаимоотношениях панели и ее менеджера

    Панель хранит ссылку на своего менеджера и сама обращается к нему каждый раз, когда нужно рассчитать координаты элементов (это происходит при их добавлении, удалении, изменении размеров, а также при изменении размеров окна). В принципе, мы можем даже получить этого менеджера методом getLayout() класса JPanel , но, как правило, в этом вообще нет необходимости.

    Кстати, класс JPanel кроме конструктора без параметров, имеет конструктор, в котором в качестве параметра задается менеджер расположения. Поэтому вместо команд

    JPanel panel = new JPanel(); panel.setLayout(new FlowLayout());

    можно написать:

    JPanel panel = new JPanel(new FlowLayout());

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

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

    Метод setContentPane(JPanel panel) позволяет заменить панель содержимого окна.

    Менеджер граничного размещения BorderLayout

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

    При добавлении элемента на панель с менеджером размещения BorderLayout , необходимо дополнительно указывать в методе add() , какая из областей имеется в виду. Для этого служат строки с названиями сторон света: "North" , "South" , "East" , "West" и "Center" . Но вместо них рекомендуется использовать константы, определенные в классе BorderLayout: NORTH , SOUTH , EAST , WEST и CENTER (поскольку в строке можно допустить ошибку и не заметить этого, а при попытке написать неправильно имя константы компилятор выдаст предупреждение). Если же использовать метод add() как обычно, с одним параметром, элемент будет добавлен в центр.

    Панель содержимого имеет именно такое расположение, именно поэтому кнопка и занимала все окно целиком (она была добавлена в центральную область). Чтобы пронаблюдать эффект BorderLayout , добавим кнопки во все пять областей:

    SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); getContentPane().add(new JButton("Кнопка" ), BorderLayout.NORTH); getContentPane().add(new JButton("+" ), BorderLayout.EAST); getContentPane().add(new JButton("-" ), BorderLayout.WEST); getContentPane().add(new JButton("Кнопка с длинной надписью" ), BorderLayout.SOUTH); getContentPane().add(new JButton("В ЦЕНТР!" )); setSize(250, 100); }

    Эффект будет хорошо наблюдаться, если изменять размеры окна.

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

    Менеджер табличного размещения GridLayout

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

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

    SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2,3,5,10)); panel.add(new JButton("Кнопка" )); panel.add(new JButton("+" )); panel.add(new JButton("-" )); panel.add(new JButton("Кнопка с длинной надписью" )); panel.add(new JButton("еще кнопка" )); setContentPane(panel); setSize(250, 100); }

    Менеджер блочного размещения BoxLayout и класс Box

    Менеджер BoxLayout размещает элементы на панели в строку или в столбец.

    Обычно для работы с этим менеджером используют вспомогательный класс Box , представляющий собой панель, для которой уже настроено блочное размещение. Создается такая панель не конструктором, а одним из двух статических методов, определенных в классе Box: createHorizontalBox() и createVerticalBox() .

    Элементы, добавленные на панель с блочным размещением, выстраиваются один за другим. Расстояние между элементами по умолчанию нулевое. Однако вместо компонента можно добавить невидимую «распорку», единственная задача которой - раздвигать соседние элементы, обеспечивая между ними заданное расстояние. Горизонтальная распорка создается статическим методом createHorizontalStrut(int width) , а вертикальная - методом createVerticalStrut(int height) . Оба метода определены в классе Box , а целочисленный параметр в каждом из них определяет размер распорки.

    Кроме того, на такую панель можно добавить еще один специальный элемент - своеобразную «пружину». Если размер панели будет больше, чем необходимо для оптимального размещения всех элементов, те из них, которые способны растягиваться, будут стараться заполнить дополнительное пространство собой. Если же разместить среди элементов одну или несколько «пружин», дополнительное свободное пространство будет распределяться и в эти промежутки между элементами. Горизонтальная и вертикальная пружины создаются соответственно методами createHorizontalGlue() и createVerticalGlue() .

    Понять особенности работы этого менеджера лучше на наглядном примере. Мы расположим четыре кнопки вертикально, поставив между двумя центральными «пружину», а между остальными - распорки в 10 пикселов.

    SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); Box box = Box.createVerticalBox(); box.add(new JButton("Кнопка" )); box.add(Box.createVerticalStrut(10)); box.add(new JButton("+" )); box.add(Box.createVerticalGlue()); box.add(new JButton("-" )); box.add(Box.createVerticalStrut(10)); box.add(new JButton("Кнопка с длинной надписью" )); setContentPane(box); setSize(250, 100); }

    Особенности выравнивания элементов

    В примере с вертикальной панелью все кнопки оказались выровнены по левому краю. Такое выравнивание по горизонтали принято по умолчанию.

    Однако при разработке окна программы может понадобиться, чтобы какие-то элементы были выровнены иначе, например, по правому краю или по центру. Для того, чтобы установить выравнивание любого визуального компонента (например, кнопки или панели), используются методы setAlignmentX(float alignment) - выравнивание по горизонтали и setAlignmentY(float alignment) - выравнивание по вертикали. В качестве параметра проще всего использовать константы, определенные в классе JComponent . Для выравнивания по горизонтали служат константы LEFT_ALIGNMENT (по левому краю), RIGHT_ALIGNMENT (по правому краю) и CENTER_ALIGNMENT (по центру). Для выравнивания по вертикали - BOTTOM_ALIGNMENT (по нижнему краю), TOP_ALIGNMENT (по верхнему краю) и CENTER_ALIGNMENT (по центру).

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

    Box.add(new JButton("-" ));

    На три других:

    JButton rightButton = new JButton("-" ); rightButton.setAlignmentX(JComponent.RIGHT_ALIGNMENT); box.add(rightButton);

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

    После запуска программы мы увидим окно, в котором кнопки расположены не так, как, наверное, ожидалось. Мы привыкли, что выравнивание по правому краю прижимает объект к правому краю контейнера, но в данном случае перестроились все элементы, причем кнопка с выравниванием по правому краю оказалась самой левой.

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

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

    о выравнивании элементов

    Параметр выравнивания на самом деле представляет собой вещественное число в диапазоне от 0 до 1. Он показывает, какая часть компонента окажется слева от линии выравнивания, т.е. в каких пропорциях компонент будет «разрезан». Константы LEFT_ALIGNMENT и TOP_ALIGNMENT на самом деле равны 0, RIGHT_ALIGNMENT и BOTTOM_ALIGNMENT равны 1, а CENTER_ALIGHNMENT - 0.5. Можно подставлять эти числа напрямую (хотя использование констант значительно повышает наглядность!), а можно выбрать любое другое число от 0 до 1 и настроить совершенно произвольное выравнивание.

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

    Ручное размещение элементов

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

    Координаты элемента можно задать одним из следующих методов:

    setLocation(int x, int y) ,

    setLocation(Point point)

    Эти методы работают аналогично, устанавливая левый верхний угол элемента в точку с заданными координатами. Разница в способе задания точки. Можно представить точку двумя целыми числами, а можно объектом класса Point . Класс Point по сути представляет собой ту же пару чисел, его конструктор имеет вид Point(int x, int y) . Получить доступ к отдельной координате можно методами getX() и getY() .

    Можно задаться вопросом: зачем использовать класс Point , если можно просто передать пару чисел? Но дело в том, что многие полезные методы возвращают результат - координаты некоторой точки - в виде объекта этого класса. Например, метод getLocation() , возвращающий координаты элемента. Предположим, нам нужно поместить элемент b в точности в то место, которое занимает элемент a . Этого легко добиться одной строкой:

    B.setLocation(a.getLocation());

    Размер элемента задается одним из двух методов:

    setSize(int width, int height) ,

    setSize(Dimension size)

    Эти методы работают одинаково - разница, как и в прошлый раз, в способе передачи параметра. Класс Dimension , аналогично классу Point , просто хранит два числа, имеет конструктор с двумя параметрами: Dimension(int width, int height) и позволяет получить доступ к своим составляющим - ширине и высоте - с помощью простых методов getWidth() и getHeigth() . Для того, чтобы получить текущий размер элемента, можно воспользоваться методом getSize() , возвращающего объект класса Dimension . Элемент b можно сделать точно такого же размера, как элемент a , выполнив команду:

    B.setSize(a.getSize());

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

    SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(null ); JButton button = new JButton("Кнопка" ); button.setSize(80, 30); button.setLocation(20,20); panel.add(button); button = new JButton("Кнопка с длинной надписью" ); button.setSize(120, 40); button.setLocation(70,50); panel.add(button); setContentPane(panel); setSize(250, 150); }

    Мы используем одну и ту же переменную button для обращения к обеим кнопкам (причем, второй раз ее описывать не нужно). В самом деле, осуществив все необходимые операции с первой кнопкой и зная, что обращаться к ней нам больше не понадобится, мы используем «освободившуюся» переменную для манипуляций со второй.

    Автоматическое определение размеров компонентов

    Если у панели есть любой менеджер размещения, она игнорирует явно заданные размеры и координаты всех своих элементов. В этом легко убедиться, заменив в предыдущем примере команду panel.setLayout(null ) на panel.setLayout(new FlowLayout()) . Менеджер размещения сам определяет координаты и размеры всех элементов.

    Способ определения координат элементов очевидным образом вытекает из алгоритмов работы каждого менеджера и, таким образом, детально рассмотрен нами выше.

    Мы также отмечали, что в некоторых случаях компоненты стараются заполнить все доступное им пространство. Например, всю центральную область в случае менеджера BorderLayout или всю ячейку в менеджере GridLayout . А в панели с менеджером FlowLayout , напротив, элементы никогда не пытаются выйти за определенные границы. Рассмотрим, что это за границы.

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

    getMinimumSize() ,

    getPreferredSize() ,

    getMaximumSize() .

    Методы возвращают результат типа Dimension . Они запрограммированы в соответствующем классе. Например, у кнопки минимальный размер - нулевой, максимальный размер не ограничен, а предпочтительный зависит от надписи на кнопке (вычисляется как размер текста надписи плюс размеры полей).

    Менеджер FlowLayout всегда устанавливает предпочтительные размеры элементов. Менеджер BorderLayout устанавливает предпочтительную ширину правого и левого, а также предпочтительную высоту верхнего и нижнего. Остальные размеры подгоняются под доступное пространство панели. Менеджер GridLayout пытается подогнать размеры всех элементов под размер ячеек. Менеджер BoxLayout ориентируется на предпочтительные размеры.

    Когда элемент старается занять все доступное ему пространство, он «учитывает» пожелания не делаться меньше своих минимальных или больше максимальных.

    Всеми тремя размерами можно управлять с помощью соответствующим методов set:

    setMinimumSize(Dimension size) ,

    setPreferredSize(Dimension size) ,

    setMaximumSize(Dimension size) .

    Чаще всего используется простой прием, когда элементу «не рекомендуется» увеличиваться или уменьшаться относительно своих предпочтительных размеров. Это легко сделать командой:

    Element.setMinimumSize(element.getPreferredSize());

    «Упаковка» окна

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

    Безусловно, наиболее подходящим будет вариант, при котором все элементы окна имеют предпочтительные размеры или близкие к ним* .

    Если вместо явного указания размеров окна, вызвать метод pack() , они будут подобраны оптимальным образом с учетом предпочтений всех элементов, размещенных в этом окне.

    SetSize(250, 100);

    на команду

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

    Упражнение

    Как уже отмечалось, элементом панели может быть другая панель. Создайте панель с тремя кнопками и менеджером размещения FlowLayout и панель с двумя кнопками и менеджером размещения BoxLayout (горизонтальным). Разместите обе панели в главном окне (не изменяя менеджера размещения у панели содержимого): одну в центр, а другую вдоль любой стороны окна.

    Рамки

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

    Рамка панели устанавливается методом setBorder(Border border) . Параметром метода выступает рамка - объект класса Border . Это абстрактный класс, поэтому для создания рамки используются его наследники:

    EmptyBorder - пустая рамка, позволяет создать отступы вокруг панели. Размеры отступов задаются в конструкторе четырьмя целыми числами.

    TitledBorder - рамка с заголовком. Простейший конструктор имеет один параметр типа String (текст заголовка). Заголовок может размещаться вдоль любой стороны рамки, иметь различные начертания.

    EtchedBorder - рамка с тиснением. Может быть вогнутой или выпуклой.

    BevelBorder - объемная рамка (выпуклая или вогнутая). Можно настроить цвета, требуемые для получения объемных эффектов.

    SoftBevelBorder - то же самое, что BevelBorder, но позволяет дополнительно скруглить углы.

    LineBorder - простая рамка, нарисованная сплошной линией. Можно выбирать цвет и толщину линии, скруглить углы.

    MatteBorder - рамка из повторяющегося рисунка.

    CompoundBorder - объединяет две рамки, передаваемые в качестве параметров конструктору в одну новую рамку.

    Все перечисленные классы описаны в пакете javax.swing.border.

    Рассмотрим пример. В этом примере мы создадим шесть панелей с различными рамками и разместим их в виде таблицы. Чтобы не описывать шесть раз процедуру создания новой панели, вынесем ее в отдельный метод:

    Private JPanel createPanel(Border border, String text) { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(new JButton(text)); panel.setBorder(new CompoundBorder(new EmptyBorder(12,12,12,12), border)); return panel; }

    Метод createPanel() создает панель с кнопкой во весь свой размер. В качестве параметра передается надпись на кнопке и рамка, которую необходимо добавить к панели. Рамка добавляется не напрямую, а путем композиции с пустой рамкой. Этот прием часто используется, чтобы рамка не прилипала к краю панели.

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

    SimpleWindow(){ super("Пробное окно" ); setDefaultCloseOperation(EXIT_ON_CLOSE); JPanel panel = new JPanel(); panel.setLayout(new GridLayout(2,3,5,10)); panel.add(createPanel(new TitledBorder("Рамка с заголовком" ), "TitledBorder" )); panel.add(createPanel(new EtchedBorder(), "EtchedBorder" )); panel.add(createPanel(new BevelBorder(BevelBorder.LOWERED), "BevelBorder" )); panel.add(createPanel(new SoftBevelBorder(BevelBorder.RAISED), "SoftBevelBorder" )); panel.add(createPanel(new LineBorder(Color.ORANGE, 4), "LineBorder" )); panel.add(createPanel(new MatteBorder(new ImageIcon("1.gif" )), "MatteBorder" )); setContentPane(panel); pack(); }

    Этот пример показывает, с помощью каких конструкторов создаются различные рамки и как они выглядят. В нем использованы два новых класса: Color и ImageIcon .

    Класс Color предназначен для работы с цветом. В нем есть несколько констант, описывающих наиболее распространенные цвета. В частности, к таковым относится Color.ORANGE .

    Класс ImageIcon описывает графическое изображение. Параметр его конструктора - это путь к файлу, из которого изображение может быть загружено. В примере используется относительное имя файла «1.gif». Чтобы объект ImageIcon был успешно создан, файл с таким именем должен быть помещен в папку проекта.

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

    И так, какие инструменты нам необходимы:

    • Java Virtual Machine (OpenJDK или Oracle JDK)
    • Intellij IDEA (или другое IDE для Java)

    После установки необходимого софта, открываем Intellij IDEA и создаем новый проект: File -> New Project…

    Я назвал проект guiBase . Как видно на скрине, папка src не содержит ничего, поэтому создаем в ней наш главный класс, содержащий функцию main .

    Public class Main { public static void main(String args) { System.out.println("Hello, Govzalla!"); } }

    Содеражние главного класса видите выше. Мы уже сейчас можем создать проект (Build project ) и запустить его (Run ). Внизу в терминале вашего IDE вы увидите сообщение “Hello, Govzalla!“ . Но как вы сами поняли — GUI он не поддерживает.

    На данном этапе у нас уже есть работающая программа, но без поддержки GUI. А сейчас в той же папке src создадим GUI Form : New -> GUI Form

    Открываем созданную GUI форму, нажимаем на JPanel и задаем его идентификатор в поле field name , я задал panel .

    После чего перетаскиваем на форму с правой стороны JTextField , JPasswordField и JButton :

    Осталось добавить код и связать нашу форму с ним. Когда мы добавляли форму MainWindow , автоматически создался и класс MainWindow , этот класс является классом созданной формы, т.е. именно этот класс будет обслуживать все события данной формы.

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

    В данный момент мы имеем форму MainWindow и класс MainWindow расширенный с помощью JFrame . Сейчас нам необходимо определить все добавленные GUI элементы как содержание класса MainWindow
    this.getContentPane().add(panel);
    После чего содержание файла MainWindow.java будет изменено следующим образом:

    Import javax.swing.*; public class MainWindow extends JFrame { private JTextField textField1; private JPasswordField passwordField1; private JButton button1; private JPanel panel; public MainWindow() { this.getContentPane().add(panel); } }

    Если попробуете запустить код, вы снова увидите то же самое сообщение “Hello, Govzalla!“. Дело в том, что мы создали класс и форму к нему, но не создали инстанцию этого класса.

    Пришло время изменить файл Main.java и добавить туда код создания нашего GUI:

    Import java.awt.*; public class Main { public static void main(String args) { // Создаем инстанцию класса MainWindow MainWindow mainWindow = new MainWindow(); // Упаковываем все элементы с нашей формы mainWindow.pack(); // Изменяем размеры окна mainWindow.setSize(new Dimension(200, 200)); // Отображаем созданное окно mainWindow.setVisible(true); } }

    Запускаем код

    Нажав на кнопку Button вы заметите, что программа никак не реагирует. Дело в том, что мы еще не добавили слушатель (Listener ) для событий (Events ) кнопки Button.

    Слушатель событий (Event listener ) JButton должен быть имплентацией адаптера ActionListener , поэтому добавим следующий код в тело класса MainWindow :

    Private class MyButtonListener implements ActionListener { @Override public void actionPerformed(ActionEvent actionEvent) { } }

    Метод actionPerformed () будет обрабатывать все события кнопки button1, но для начала еще необходимо указать кнопке button1 какой класс будет обрабатывать, поэтому добавим следующий код в конструктор класса MainWIndow:
    this.button1.addActionListener(new MyButtonListener());
    Чтобы наш обработчик не был бессмысленным добавим следующий код в метод actionPerformed ():

    @Override public void actionPerformed(ActionEvent actionEvent) { if (textField1.getText().equals(passwordField1.getText())) { JOptionPane.showMessageDialog(null, "Success"); } else { JOptionPane.showMessageDialog(null, "Failure"); } }

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

    JFace — это доплнительный слой абстракции над графической библиотекой SWT, предоставляющий возможности для разработки графического интерфейса приложения на основе модели MVC(Model View Controller). Основные компоненты JFace включают:

    • Viewers — классы для инкапсуляции задач предоставления данных для отображения, их фильтрации, сортировки и т.п.
    • Actions and contributions — вводят семантику описания пользовательских действий и того, как они должны быть доступны пользователю.
    • Image and font registries — предоставляют классы для управления ресурсами, такими как изображения и шрифты.
    • Dialogs and wizards — фреймворк для построения сложного диалогового взаимодействия с пользователем.
    • Field assist — предоставляет возможности для реализации вспомогательной функциональности для полей, такой как отображение статуса поля или подсказки о содержимом.

    В первой части будет рассмотрено создание простого окна с использованием JFace, добавление к нему меню, статусной строки и панели инструментов.

    Настройка проекта

    Для того, чтобы разрабатывать графические приложения с использованием JFace в Eclipse, необходимо подключить jar-файлы SWT и JFace.

    • В случае, если в Eclipse установлено PDE, то это можно сделать, указав в настройках проекта использовать библиотеку SWT, и поставить галочку для поддержки JFace.
    • Если же PDE не установлен, то необходимо подключить jar-файлы к проекту вручную. Найти их можно в директории plugins эклипса, называются они org.eclipse.swt_*.jar и org.eclipse.jface_*.jar

    Создание окна

    Окно приложения в JFace наследуется от класса ApplicationWindow, предоставляющего возможности для создания меню, панелей управления и строки статуса.

    Простейший вариант создания окна приведен в следующем примере:

    import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.swt.widgets.Display; public class MainWindow extends ApplicationWindow { public static void main(String args) { MainWindow window = new MainWindow(); // Создаем наше окно window.setBlockOnOpen(true); // Устанавливаем флаг - ждать закрытия окна window.open(); // Открываем окно Display.getCurrent().dispose(); // Освобождаем ресурсы } public MainWindow() { super(null); // Вызываем конструктор родительского класса } }

    Создание содержимого окна

    Для того, чтобы создать какое-то содержимое окна, необходимо переопределить метод Control createContents(Composite parent) класса ApplicationWindow. Метод должен возвращать компонент, который будет являться содержимым окна.

    В этом же методе можно установить и заголовок окна, вызвав метод setText(String text) шелла, доступного по вызову getShell()

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

    Собственно, пример:

    protected Control createContents(Composite parent) { getShell().setText("My window"); // Устанавливаем заголовок окна Label lbl = new Label(parent, SWT.NONE); // Создаем новый элемент, в нашем случае - просто метку lbl.setText("Window contents"); // Устанавливаем ей текст lbl.setBackground(new Color(Display.getCurrent(), 255, 255, 255)); // Устанавливаем белый цвет фона return lbl; }

    Добавление статусной строки

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

    Для того, чтобы отобразить текст в статусной строке необходимо вызвать метод setStatus(String status), аргументом которого и является та самая строка, которую необходимо отобразить.

    Создание меню

    Для создания строки меню в JFace необходимо, вызвать метод addMenuBar(), как и для создания строки статуса.
    Но затем необходимо добавить пункты меню на панель. Осуществляется это следующим образом:

    • Раздел меню описывается классом MenuManager. В конструкторе ему может быть передана строка — имя раздела, где символ «&» означает, что следующий за ним символ будет ключом при навигации с помошью клавиши Alt. Добавляется раздел меню на панель с помощью конструкции getMenuBar().add(menu).
    • Пункт меню описывается классом Action. Сам класс является абстрактным, пункт меню должен быть унаследован от него и переопределять метод void run(), в котором размещается код, выполняемый при выборе пункта меню пользователем. На самом деле, метод можно и не переопределять, но тогда, зачем этот пункт меню нужен?=) Имя пункта может быть задано путем передачи в конструктор, или вызовом метода void setText(String text). После создания пункт меню добавляется в раздел вызовом метода add у объекта раздела меню. Например: menu.add(menuItem)
    • Подменю создаются очень просто: Необходимо в один раздел меню методом add добавить другой раздел. Вот и все.

    В следующем примере мы создаем раздел меню File и один пункт Hello в нем, выбор которого вызывает появление текста в статусной строке окна. Код создания меню я оформил в один метод, который вызывается из конструктора окна:

    private void createMenu() { addMenuBar(); // Добавляем панель меню MenuManager fileMenu = new MenuManager("&File"); // Создаем новое меню getMenuBarManager().add(fileMenu); // Добавляем меню на панель fileMenu.add(new Separator()); // Добавляем разделитель в меню fileMenu.add(new Action("&Hello") { // Создаем новое действие, указываем его текст @Override public void run() { // Код выполняемый при активации действия setStatus("Hello world!!"); // Мы просто отображаем новое значение в статусной строке } }); // И добавляем действие в качестве пункта меню }

    Создание панели инструментов

    Для того, чтобы создать панель инструментов в окне необходимо в конструкторе окна вызвать метод addToolBar(int style), в который передать стиль компонента панели.

    Для доступа к созданной панели используется метод getToolBarManager(). Для добавления действия на панель используется метод add панели, куда передается действие.

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

    private void createToolBar() { addToolBar(SWT.NONE); getToolBarManager().add(new Action("&Hello") { // Создаем новое действие, указываем его текст @Override public void run() { // Код выполняемый при активации действия setStatus("Hello world!!"); // Мы просто отображаем новое значение в статусной строке } }); }

    Создание нескольких перемещаемых панелей инструментов

    Вместо одной панели инструментов можно создать набор перемещаемых панелей (CoolBar). Для этого необходимо:

    1. Вместо метода addToolBar вызвать метод addCoolBar
    2. Для доступа к набору панелей используется метод getCoolBarManager()
    3. Создать панель инструментов, на которую можно будет добавлять действия. Для этого необходимо создать новый экземпляр класса ToolBarManager, который будет представлять панель и вызвать метод add у CoolBarManager, передав туда панель.
    4. На созданную панель можно добавлять действия

    Пример кода, создающего две плавающие панели:

    private void createCoolbar() { addCoolBar(SWT.NONE); ToolBarManager tm = new ToolBarManager(); getCoolBarManager().add(tm); tm.add(helloAction); ToolBarManager tm2 = new ToolBarManager(); getCoolBarManager().add(tm2); tm2.add(helloAction); tm2.add(helloAction); }

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

    Возможные проблемы

    Здесь будут описаны некоторые проблемы, с которыми можно столкнуться:

    • При добавлении пунктов меню после запуска приложения они не появляются — чтобы изменения меню после создания окна отобразились на нем, необходимо вызвать метод getMenuBarManager().update(true) — указать обновить панель меню.

    Ссылки

    Дополнительная информация по JFace может быть найдена по следующим ссылкам:

    • Русскоязычная вводная статья на ibm.com
    • Цикл англоязычных статей на ibm.com
    • Описание API Eclipse — среди прочего там есть и пакеты, относящиеся к SWT и JFace