140 |
ГЛАВА 6 |
инструмент, который вы можете бесплатно скачать и присоединить к приложению
ввиде JAR-файла, обладает более широкими возможностями, так что рассмотрим мы его, ну а в дальнейшем мы вольны выбирать его или стандартную реализацию
вJDK.
Суть инструмента JXLayer проста — часть уже имеющегося интерфейса, например отдельная панель, или весь он, это может быть панель содержимого вашего окна, помещается в JXLayer как в контейнер, а над ним размещается слой (layer, отсюда и название), в котором вы, во-первых, можете рисовать все, что захотите над всем интерфейсом, во-вторых, можете получать все события, которые над ним происходят. Разумеется, JXLayer необходимо разместить на экране, как правило вместо того интерфейса, который вы собираетесь дополнить слоем. JXLayer можно вкладывать друг в друга, получая несколько слоев. Дополнительные варианты слоев позволяют получить новые возможности, к примеру слой с буферизацией (BufferedLayerUI) сохраняет всю картину интерфейса в виде изображения в памяти, и вы можете применить к нему эффекты и преобразования (размыть, перевести в черно-белый цвет и т.п.). Интересным вариантом слоя является LockableUI, позволяющий не только отключить все события для компонента «под ним», но и сразу же применить какие-либо эффекты к нему как к изображению, что очень удобно при создании модальных эффектов и легковесных диалогов.
Рассмотрим пример, чтобы удостовериться, что все сказанное о JXLayer правда и работает на самом деле:
//JXLayerTest.java
//Пример использования слоев JXLayer import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.BufferedLayerUI; import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
public class JXLayerTest extends JFrame { public JXLayerTest() {
super("JXLayerTest");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
//создадим простое содержимое
JPanel p = new JPanel(); p.add(new JTextField(10)); p.add(new JButton("ОК"));
// поместим содержимое в слой
JXLayer<JPanel> layer = new JXLayer<JPanel>(p); // создадим слой реагирующий на события layer.setUI(new BufferedLayerUI<JPanel>() {
private int lastX, lastY; // прорисовка слоя
public void paint(Graphics g, JComponent c) {
Контейнеры высшего уровня |
141 |
super.paint(g, c); g.setColor(Color.RED); g.fillOval(lastX, lastY, 15, 15);
}
// получение событий в пределах слоя
protected void processMouseMotionEvent(MouseEvent e, JXLayer<? extends JPanel> layer) {
lastX = SwingUtilities.convertMouseEvent( (Component) e.getSource(), e, layer).getX();
lastY = SwingUtilities.convertMouseEvent( (Component) e.getSource(), e, layer).getY();
repaint();
}
});
//JXLayer необходимо добавлять в контейнер add(layer);
//выведем окно на экран
setSize(400, 150); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new JXLayerTest(); } });
}
}
Здесь мы создаем окно, в котором хотим разместить текстовое поле и кнопку, однако не напрямую, а вместе со слоем. При работе с инструментом JXLayer сразу стоит отметить, что он практически полностью параметризован, то есть требует указать при создании, какой конкретно тип компонента будет покрыт слоем, этот же тип передается и в сам слой. В нашем случае это будет панель JPanel, в которой будут располагаться компоненты. Ее необходимо передать в JXLayer как «основу», а метод setUI() задает слой в виде объекта LayerUI.
В нашем примере мы унаследовали от стандартного слоя BufferedLayerUI, хранящего изображение интерфейса в памяти, и переопределили метод для рисования paint(), и метод, получающий события о передвижениях мыши в пределах слоя. Обратите внимание, что у слоев компонента JXLayer нет слушателей, есть лишь знакомые нам по системе событий методы processXXXEvent(). Мы следим за перемещениями мыши в пределах слоя и рисуем рядом с курсором маленький красный кружок. Заметьте, что события, происходящие в слое, все равно распределены по компонентам, и если мы хотим получить координаты в слое, а не в текстовом поле или кнопке, нам всегда необходимо преобразовывать их с помощью класса SwingUtilities. Так устроена система событий Swing, и это надо учитывать при «глобальной» обработке событий от нескольких компонентов.
По окончанию настройки компонент JXLayer добавляется в центр окна и последнее выводится на экран. Запустив пример, вы увидите, что интерфейс функционирует как
142 |
ГЛАВА 6 |
обычно, получая все события, слой же получает их все, и способен изменять визуальное представление интерфейса на экране, рисуя на экране красный кружок у курсора вне зависимости от того, над каким компонентом находится мышь. Кстати, для запуска примера вам понадобится скачать библиотеку JXLayer из Интернета и добавить ее в свой системный путь CLASSPATH.
Как мы видим, компонент JXLayer прекрасно справляется с несовершенствами других способов работы поверх множества компонентов. Он не является разделяемым ресурсом системы и не требует осведомленности обычных компонентов о присутствии дополнительного слоя. Вы сможете применить к интерфейсу любые эффекты, в том числе эффект сворачивания и разворачивания страниц, размытие, постепенное проявление и любые другие. В простых же случаях можно обратить особое внимание на слой LockableUI, который парой методов позволяет отключить интерфейс от событий и применить к нему простые графические эффекты. Очень удобно при создании дополнительных эффектов внутри одного окна, контейнера или даже панели без лишних затрат.
Окна Swing
Окна — это основа пользовательского интерфейса любой современной операционной системы. Они визуально разделяют выполняемые пользователем задачи, позволяя ему работать более эффективно. Мы уже отмечали, что окна, используемые в библиотеке Swing, мало чем отличаются от окон библиотеки AWT (а те в свою очередь представляют собой окна операционной системы), от которых наследуют, исключая корневую панель, в которой нам и приходится размещать все компоненты своего пользовательского интерфейса. Все окна библиотеки Swing — а к ним относятся окно без рамки JWindow, окно с рамкой JFrame и диалоговое окно JDialog — являются исключением из правила, согласно которому все компоненты Swing представляют собой легковесные компоненты и унаследованы от базового класса JComponent. Они наследуют напрямую от окон AWT и являются тяжеловесными контейнерами. Мы уже обсуждали, что это необходимо для легковесных компонентов — операционная система их не видит, так что размещать такие компоненты нужно в тяжеловесных контейнерах, иначе они не смогут прорисовываться и получать информацию о событиях. Для этого и предназначены окна Swing. Знакомиться с ними начнем мы с «родителя» всех окон Swing — окна без рамки JWindow.
Окно без рамки JWindow
Класс JWindow представляет собой окно без рамки и без элементов управления (предназначенных, к примеру, для его закрытия или перемещения), оно дает пользователю минимальные возможности по своей настройке (в отличие от используемого чаще всего окна JFrame). Окно без рамки не так уж часто требуется в программах, однако иногда оно может быть полезно, особенно в тех случаях, когда вам необходимо ненадолго вывести на экран какую либо информацию, такую как заставку своей программы или подсказку для пользователя, и управлять окном с этой информацией не
Контейнеры высшего уровня |
143 |
нужно. К примеру, окна JWindow используются всплывающими меню JPopupMenu в тех ситуациях, когда в окне приложения не хватает места для размещения легковесного компонента в слое POPUP многослойной панели, где всплывающие меню располагаются по умолчанию. В такой ситуации вместо легковесного компонента создается небольшое окно без рамки JWindow, которое можно разместить в любом месте экрана, потому что оно принадлежит операционной системе. В этом окне и размещается всплывающее меню.
Мы рассмотрим один из вероятных способов применения окна без рамки JWindow — в качестве постоянно висящей на переднем плане рабочего стола «панели инструментов», которую нельзя закрыть или скрыть. Давайте попробуем:
//FloatingWindow.java
//Окно без рамки, всегда остающееся
//выше основного окна приложения import javax.swing.*;
public class FloatingWindow extends JFrame { public FloatingWindow() {
super("FloatingWindow");
//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);
//выведем главное окно на экран setSize(400, 300); setVisible(true);
//добавим плавающее окно
JWindow window = new JWindow(this);
//всегда над другими окнами window.setAlwaysOnTop(true);
//выведем окно на экран window.setSize(100, 300); window.setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new FloatingWindow(); } });
}
}
В этом примере мы наследуем от окна JFrame и выводим его на экран. Далее создается дочернее окно JWindow, которому мы передаем ссылку на родительское окно. Используя свойство alwaysOnTop, мы просим систему держать наше окно без рамки выше всех остальных окон и также выводим его на экран. Если вы уже пользуетесь выпуском Java 7, вы также сможете задействовать свойство type, чтобы поменять тип окна на вспомогательное (utility). Запустив пример, вы убедитесь в том, что непритязательный
144 |
ГЛАВА 6 |
серый прямоугольник действительно всегда перекрывает остальные окна вашего рабочего стола.
Обратите внимание на то, что, как правило, окна без рамки JWindow требуют при создании указывать своего «родителя»5 — окно с рамкой JFrame или другое окно, унаследованное от класса Window, что иногда может быть неудобно. Специально для таких случаев в класс JWindow был добавлен конструктор без параметров, который создает вспомогательное невидимое окно JFrame и использует его как «родителя». После этого все окна без рамки, созданные таким образом, задействуют только это окно и экономят ресурсы. Стоит также сказать, что с помощью конструктора без параметров создается окно JWindow, неспособное получать фокус ввода. Чаще всего именно такое поведение необходимо (ведь панелям инструментов, всплывающим заставкам и меню фокус ввода не нужен), а если вы хотите исправить ситуацию и получать в свое окно без рамки фокус ввода, используйте метод setFocusableWindowState(true).
Рассказать что-либо очень интересное об окне без рамки JWindow и остальных окнах Swing очень трудно, потому что они являются лишь тонкой оболочкой для окон операционной системы, к которым у вас нет доступа (так сделано для лучшей переносимости — вы можете использовать только те свойства, которые гарантированно поддерживаются на всех платформах). Чаще всего вы просто создаете окна и добавляете в принадлежащую им корневую панель свои компоненты. Более того, лучше вообще свести к минимуму количество разнообразных плавающих окон в своем приложении, тем более что у нас нет достаточного контроля над ними, а любой дополнительный необязательный объект приложения на экране, да еще и «висящее» окно, крайне раздражает. Поэтому мы ограничимся списком методов, наиболее часто используемых для окон Swing (табл. 6.2).
Таблица 6.2. Наиболее полезные методы окон Swing
Методы |
Описание |
|
|
setLocation(), setSize(), |
Эта группа методов позволяет задать позицию и размеры окна на |
setBounds() |
экране (на рабочем столе пользователя). Первый метод задает по- |
|
зицию окна, второй позволяет указать его размеры, а с помощью тре- |
|
тьего вы сможете сразу задать прямоугольник, который займет ваше |
|
окно на экране |
pack() |
Позволяет «упаковать» имеющиеся в окне компоненты, так чтобы |
|
они занимали столько места, сколько им необходимо, а не боль- |
|
ше и не меньше. Подробнее об этом методе мы узнаем в главе 5. |
|
Интересно, что компоненты при вызове этого метода переходят |
|
в «видимое» состояние, то есть начинают использовать ресурсы |
|
системы, хотя и не появляются на экране до вызова следующего |
|
метода |
setVisible() |
Выводит ваше окно на экран или скрывает его с экрана (если параме- |
|
тром указано значение false) |
dispose() |
Убирает окно с экрана (если оно в момент вызова метода видимо) |
|
и освобождает все принадлежащие ему ресурсы. Если в вашем |
|
приложении много окон, во избежание нехватки памяти не забы- |
|
вайте вызывать этот метод для тех из них, что больше не исполь- |
|
зуются |
5 Здесь в библиотеке небольшая путаница: класс JFrame унаследован от JWindow, и в тоже время конструктор JWindow требует передачи параметра JFrame.