Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Портянкин И. Swing

.pdf
Скачиваний:
141
Добавлен:
07.10.2020
Размер:
4.63 Mб
Скачать

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

135

processMouseEvent() мы делаем следующее: выясняем, что произошло нажатие кнопки мыши, а не что-либо еще (используя идентификатор типа события) и начинаем поиск компонента, на котором пользователь щелкнул мышью. Здесь нам помогает метод класса SwingUtilities getDeepestComponentAt(), позволяющий узнать, находится ли какой-либо видимый компонент в указанной точке. Он сделает всю работу, в том числе пройдет по всем вспомогательным панелям и контейнерам. Правда, напрямую передавать в него точку, хранящуюся в событии MouseEvent, нельзя, потому что эта точка находится в пространстве координат прозрачной панели, а метод getDeepestComponentAt () мы хотим применить в пространстве координат панели содержимого. Как раз для таких ситуаций в классе SwingUtilities имеется статический метод convertMouseEvent(), создающий новый объект MouseEvent в нужной системе координат. Далее мы вызываем помощь для компонента, на котором был выполнен щелчок мыши (если такой компонент обнаруживается). В заключение мы делаем прозрачную панель невидимой, чтобы интерфейс мог снова получать события, и меняем указатель мыши на обычный. Это поможет пользователю определить, что интерфейс снова действует в обычном режиме.

Наш пример — всего лишь прототип системы помощи, однако его легко приспособить под нужды реальных приложений. Достаточно заменить класс HelpSystem, использовав вместо него более мощную справочную систему, основанную, например, на языке разметки HTML, поддерживаемом в Swing, и придумать более удобный способ сопоставления компонента и справки для него. Удачным решением может быть обращение к клиентским свойствам компонентов Swing; с их помощью вы можете задать для каждого компонента (не наследуя от него) уникальный идентификатор, который и будет задействован в системе помощи для поиска нужной информации4.

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

4 Если вы думаете о приложении с мощной справочной системой, попробуйте использовать пакет JavaHelp, созданный компанией Sun; вы сможете найти его на сайте java.sun.com. Он наделен всеми возможностями современных справочных систем и легко встраивается в приложение.

136

ГЛАВА 6

Вы видите, что прозрачная панель с легкостью позволяет вам работать поверх пользовательского интерфейса программы, как бы он ни был сложен. Мы уже придумали ей несколько применений, можно еще добавить, что упрощается создание обучающих систем: вы можете временно отрезать события от большинства компонентов, рисуя подробные комментарии поверх них и позволяя пользователю постепенно знакомиться с интерфейсом программы. Это может быть очень эффектно и способно значительно поднять привлекательность вашего приложения. Сделать это довольно просто — чтобы задать прозрачной панели форму, в пределах которой она будет перехватывать события от мыши, необходимо применить метод contains(), который мы обсуждали в главе 2. Рисовать же вполне можно в пределах всего окна, особенно полупрозрачными кистями.

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

Корневая панель — итог

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

Строка

меню Корневая

панель

Многоуровневая

панель

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

Панель

содержимого

Рис. 6.4. Корневая панель

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

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

137

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

Корневая панель позволяет получить или изменить все свои составляющие части, для этого у нее имеется набор методов get/set (например, уже использованные нами методы getContentPane() и setContentPane()). В любом контейнере высшего уровня Swing вы можете получить задействованную в нем корневую панель, вызвав метод getRootPane(). Для удобства в контейнерах высшего уровня также имеются методы get/set, позволяющие напрямую получать или изменять составные части корневой панели. Кроме контейнеров высшего уровня, которые мы уже упоминали, корневая панель также применяется во внутренних окнах JInternalFrame, создаваемых в многодокументных приложениях и располагающихся на «рабочем столе» JDesktopPane. Это позволяет забыть про то, что данные «окна» представляют собой обычные легковесные компоненты, и работать с ними как с настоящими контейнерами высшего уровня, а также обеспечивает их правильное поведение.

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

//LayeredPaneEffects.java

//Создание эффектов для интерфейса

//с помощью многослойной панели import javax.swing.*;

import java.awt.*;

public class LayeredPaneEffects extends JFrame { public LayeredPaneEffects() {

super("LayeredPaneEffects"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//несколько обычных кнопок и текстовое поле

JPanel buttons = new JPanel(); buttons.add(new JButton("Применить")); buttons.add(new JButton("Записать")); buttons.add(new JTextField(20));

//добавляем в панель содержимого getContentPane().add(buttons);

//добавляем компонент с анимацией в слой PALETTE Animation an = new Animation();

an.setBounds(50, 10, anim.getWidth(this), anim.getHeight(this));

getLayeredPane().add(an, JLayeredPane.PALETTE_LAYER);

//выводим окно на экран

138

ГЛАВА 6

setSize(250, 100); setVisible(true);

}

//изображение private Image anim =

new ImageIcon("anim.gif").getImage();

//компонент, рисующий анимированное изображение class Animation extends JComponent {

public Animation() { setOpaque(false);

}

public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g;

//полупрозрачность

g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.3f));

//рисуем изображение

g2.drawImage(anim, 0, 0, this);

}

// мы никогда не получаем событий от мыши public boolean contains(int x, int y) {

return false;

}

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new LayeredPaneEffects(); } });

}

}

В примере мы создаем окно JFrame, в которое добавляем несколько обычных элементов пользовательского интерфейса: пару кнопок и текстовое поле. Затем в слое PALETTE многослойной панели размещается специальный компонент Animation, который является прозрачным (мы устанавливаем его свойство непрозрачности равным false) и занимается тем, что прорисовывает на экране анимированное изображение (в формате GIF, который полностью поддерживается Java). В методе paintComponent() этого компонента мы используем возможности библиотеки Java2D, чтобы сделать изображение полупрозрачным (иначе оно слишком сильно будет загораживать обычный интерфейс). Обратите внимание на метод contains(), который определяет, попадает ли указанная точка экрана в область компонента. Так как у нас прозрачный компонент, необходимо всегда говорить системе событий что ни одна точка в компонент не попадает, а иначе у нас будут проблемы, например, с курсором в текстовом поле, так как курсор будет использоваться от нашего декоративного компонента, а не текстовый.

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

139

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

Может возникнуть справедливый вопрос, почему же мы не поместили изображение в прозрачную панель. Конечно, это возможно, однако многослойная панель может быть более гибкой. Мы поместили наш анимационный компонент в слой PALETTE, который находится ниже, чем слой POPUP, предназначенный для всплывающих меню. Если мы присоединим к текстовому полю такое меню, оно будет показано над нашей анимацией, что более логично. Тоже самое относится и к операциям перетаскивания, также отображаемых в многослойной панели. А используй мы прозрачную панель, которая «нависает» над всем окном, у нас не было бы простой возможности решить, какие компоненты и операции анимация перекрывает, а какие нет. Более того, мы уже знаем что прозрачная панель является общим ресурсом и представляет собой один единственный компонент, в то время как в многоуровневую панель можно добавить сколь угодно много компонентов подобных тем что мы создали в примере. Перефразируя, можно сказать, что если есть возможность не применять прозрачную панель, лучше этого не делать, и мы здесь последовали этому примеру.

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

Расширение границ — J(X)Layer

Корневая панель, доступная в любом контейнере высшего уровня Swing, дает нам прекрасные возможности, в том числе способ перехватывать события от мыши, рисовать над остальными компонентами украшения и применять какие-либо художественные эффекты. Однако, при рассмотрении прозрачной панели мы сразу же увидели первый недостаток — практически невозможно «отключить» события от клавиатуры, и таким образом создать некоторый аналог легковесных модальных диалогов, перехватывающих все события от основного интерфейса. Также нет возможности «видеть» события не от конкретных компонентов, а от некоторой области на экране, в которой может находиться множество таких компонентов. Сама система событий Swing и AWT построена так, что все события приходят к конкретным компонентам, а не в какую-то ограниченную область.

Специально, чтобы избавиться от всех этих ограничений и наделить Swingприложения расширенными возможностями, Александр Поточкин из команды разработчиков Swing создал инструмент под названием JXLayer. Поначалу этот инструмент развивался отдельно, а потом стал постепенно переводиться в библиотеку Swing в версии JDK 1.7 под названием JLayer. На момент написания книги отдельный

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.