120 |
ГЛАВА 5 |
JButton button = new JButton("Сменить"); button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // при нажатии сменим алгоритм для окна setFocusTraversalPolicy(
new ContainerOrderFocusTraversalPolicy());
}
});
add(button, "South");
add(new JButton("Правая"), "East"); // выводим окно на экран setSize(200, 200); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new FocusPolicyTest(); } });
}
}
Здесь мы создаем небольшое окно с тремя кнопками, причем добавляются они в окно таким образом, чтобы порядок расположения их в окне не совпадал с порядком добавления их в контейнер. По умолчанию в окне используется полярное расположение, его мы и учли для получения такого эффекта, добавив вторую по счету кнопку в нижнюю часть окна. К ней добавляется слушатель событий, обрабатывающий нажатие кнопки и меняющий при этом алгоритм передачи фокуса в окне.
Запустив программу с примером, вы сможете оценить работу двух различных алгоритмов передачи фокуса ввода. Сначала действует алгоритм LayoutFocusTraversalPolicy, и фокус ввода передается так, как располагаются кнопки в окне. Сменив алгоритм на ContainerOrderFocusTraversalPolicy, вы увидите, во-первых, что теперь фокус переходит так, как мы добавляли кнопки в окно (и с точки зрения пользователя это довольно неудобно), а, во-вторых, что на некоторое время фокус исчезает в каких-то невидимых компонентах. Это проблем, так как система считает, что любой компонент нуждается в фокусе ввода, хотя корневая панель в нем не нуждается. Именно в корневой панели и «застревает» ненадолго фокус ввода. Алгоритм LayoutFocusTraversalPolicy справляется с этой проблемой, потому что дополнительно проверяет старые свойства, связанные с фокусом ввода, определенные в классе JComponent еще до появления новой системы. Поэтому лучше не использовать в приложениях Swing алгоритмы передачи фокуса AWT.
Расширенные возможности
В системе передачи фокуса ввода также есть несколько дополнительных возможностей. Прежде всего стоит отметить понятие цикла передачи фокуса (focus cycle) и его корня (focus cycle root). Фокус ввода передается только в пределах цикла передачи фокуса, который представляет собой набор из некоторых компонентов. Компоненты находятся
Внутренние винтики Swing |
121 |
в контейнере, который называется корнем цикла. По умолчанию в качестве корня используются только контейнеры высшего уровня, и все добавляемые в них компоненты оказываются в одном цикле, однако корнем можно сделать любой контейнер, что может быть полезным для очень больших контейнеров, напичканных компонентами. Для перехода из одного цикла передачи фокуса в другой применяется специальное множество клавиатурных сокращений, его позволяет задать класс KeyboardFocusManager. Чтобы заставить контейнер вести себя как корень цикла, необходимо переопределить его метод isFocusCycleRoot() так, чтобы он возвращал значение true. Тем не менее, в обычных приложениях с обычными пользовательскими интерфейсами создание дополнительных циклов передачи фокуса требуется редко, поскольку лишь «добавляет пользователям головной боли».
Далее стоит сказать о возможностях окон Java, унаследованных от класса Window. Благодаря усилиям класса KeyboardFocusManager, у окон AWT имеется возможность отказываться от фокуса ввода. Для отключения возможности получения фокуса ввода в окне вы можете использовать метод setFocusableWindowState(false). После этого компоненты, расположенные в окне, не будут получать сообщений от клавиатуры. События (описанные в интерфейсе слушателя WindowFocusListener) позволяют различить активизацию окна (в общем случае это означает, что пользователь использовал окно последним) и получение окном фокуса ввода (что может произойти только в окне, которое соглашается обладать фокусом ввода). Интересно, что события, сообщающие о получении фокуса ввода, полностью создаются внутри Java классом KeyboardFocusManager, а не приходят из операционной системы.
Также благодаря тому, что система передачи фокуса ввода полностью написана на Java, появилась возможность программно управлять передачей фокуса, и вдобавок получать исчерпывающую информацию о состоянии системы и компонентах, обладающих фокусом. Все это также относится к функциям класса KeyboardFocusManager. С его помощью вы сможете легко передать фокус следующему, предыдущему или произвольному компоненту, а также узнать, какой компонент обладает в данный момент фокусом ввода и какое окно является активным. Более подробно познакомиться с возможностями системы передачи фокуса ввода и методами ее центрального класса KeyboardFocusManager можно в интерактивной документации Java.
Всплывающие подсказки и клиентские свойства
Кроме тех важнейших базовых возможностей, которыми обеспечивает библиотеку Swing класс JComponent и которые мы только что рассмотрели, у него осталось еще несколько обязанностей. С ними он также справляется с блеском, и представить Swing без этого просто невозможно.
Очень полезным свойством всех без исключения компонентов Swing является возможность показывать всплывающие подсказки (tool tips) при небольшой задержке указателя мыши над компонентом. Поддержка Swing всплывающих подсказок также обеспечивается базовым классом JComponent. В нем определен метод setToolTipText(), в который вы передаете текст подсказки. При передаче в данный метод текста JComponent регистрирует компонент во вспомогательном классе ToolTipManager. Последний представляет собой одиночку и централизованно управляет процессом вывода подсказок в системе. При регистрации компонентов он присоединяется к ним в качестве слушателя событий от мыши, и при появлении указателя мыши на компоненте запускает таймер, следящий за временем, в течение которого указатель мыши остается на компоненте неподвижным, и таким образом определяет, когда следует вывести подсказку. Подробнее о всплывающих подсказках мы узнаем в главе 8.
Наконец, в арсенале класса JComponent есть еще одно оружие — клиентские свойства (client properties), весьма удачная находка создателей Swing. Эти свойства практически
122 |
ГЛАВА 5 |
аналогичны свойствам JavaBeans, только они не требуют наличия в классе компонента поля, хранящего значение самого свойства, и пары методов get/set. Вместо этого значение свойства хранится в специальном ассоциативном массиве, а для его изменения
иполучения используется пара методов putClientProperty() и getClientProperty(). Клиентские свойства, как и свойства JavaBeans, являются привязанными и позволяют настраивать
иизменять компонент без его переписывания и наследования от него. Иногда это может быть очень полезно, например при появлении свойств, которые еще не совсем готовы или чересчур усложняют компонент. В Swing клиентские свойства используются очень широко, особенно для настройки внутренней работы компонентов и их взаимодействия, а также для наделения компонента новыми свойствами, которые предположительно (но не обязательно) станут затем обычными свойствами JavaBeans, а пока просто используются для изучения реакции разработчиков на нововведение (как это было, например, с плавающими панелями инструментов). Клиентские свойства весьма удобны при разработке новых компонентов, позволяя сократить количество вспомогательных методов и полей, их также удобно задействовать при добавлении к уже существующим компонентам новых возможностей, когда наследование от компонентов излишне громоздко. Впрочем, злоупотреблять ими не следует, и если у вас появляется с десяток свойств, реализованных в виде клиентских, стоит подумать о создании нового компонента с обычными свойствами JavaBeans.
Резюме
Внутренние «винтики» Swing, незаметно для нас крутящиеся внутри всех компонентов, в основном скрыты в базовом классе библиотеки JComponent. Все компоненты библиотеки наследуют от него и поэтому поддерживают основные свойства библиотеки, как-то: всплывающие подсказки, улучшенные системы рисования и проверки корректности, клавиатурные сокращения и многое другое. Как правило, вдаваться в детали внутренних процессов Swing приходится редко, но если вам понадобилось особым образом настроить работу своих компонентов, знание о том, что происходит внутри, оказывается как нельзя кстати.
Глава 6. Контейнеры высшего уровня
После создания пользовательского интерфейса необходимо вывести его на экран, чтобы пользователь увидел его и смог им воспользоваться. Для этого предназначены специальные компоненты библиотеки Swing, называемые контейнерами высшего уровня (top level containers). Они представляют собой окна операционной системы, в которых вы размещаете компоненты своего пользовательского интерфейса. К контейнерам высшего уровня относятся окна JFrame и JWindow, диалоговое окно JDialog, а также апплет JApplet (который не является окном, но тоже предназначен для вывода интерфейса в браузере, запускающем этот апплет). В этой главе мы также рассмотрим компонент- «рабочий стол» JDesktopPane и внутренние окна JInternalFrame, которые хоть формально и не являются контейнерами высшего уровня, но по сути выполняют такие же задачи.
Мы говорили, что все компоненты библиотеки Swing являются легковесными и не требуют поддержки операционной системы — это верно, но только не для контейнеров высшего уровня. Они представляют собой тяжеловесные компоненты (точнее, контейнеры) и являются исключением из общего правила. Ничего страшного в этом исключении нет: достаточно вспомнить, что легковесные компоненты (к ним относятся все компоненты Swing) являются просто областью экрана некоторого тяжеловесного контейнера, который любезно предоставляет им свое пространство. Как раз контейнеры высшего уровня и предоставляют остальным компонентам Swing пространство экрана для работы, а также следят, чтобы они вовремя получали сообщения операционной системы о перерисовке и возникновении событий (как мы знаем, операционная система понятия не имеет о существовании легковесных компонентов).
На самом деле оказывается, что контейнеры высшего уровня Swing — это самые незатейливые компоненты библиотеки. Они просто наследуют от соответствующих классов AWT (Frame, Window, Dialog и Applet), а также, чтобы смягчить разницу между контейнерами AWT и компонентами Swing, застилают свое пространство специальной «соломкой» в виде так называемой корневой панели, которая обеспечивает легковесные компоненты некоторыми возможностями, отсутствующими в контейнерах AWT (например, специальным местом для строки меню Swing, о которой AWT знать не знает). На этом работа контейнеров высшего уровня Swing заканчивается. Можно попробовать разместить компоненты Swing напрямую в окнах AWT, но в результате получится не слишком работоспособный интерфейс, так как корневая панель предоставляет компонентам Swing весьма важные услуги.
Поэтому, прежде чем перейти к рассмотрению контейнеров высшего уровня Swing (а они очень просты), мы поподробнее рассмотрим корневую панель и ее роль в создании интерфейса и функционировании некоторых компонентов.
Корневая панель JRootPane
Каждый раз, когда вы создаете новый контейнер высшего уровня, будь то обычное окно, диалоговое окно или апплет, в конструкторе этого контейнера создается особый компонент — экземпляр класса JRootPane, который добавляется в контейнер, причем так, чтобы занять все доступное в контейнере место. Более того, все контейнеры высшего уровня Swing следят за тем, чтобы другие компоненты не смогли «пробраться» за пределы этого специального компонента: они переопределяют метод add(), предназначенный для добавления компонентов, и при вызове этого метода принудительно добав-
124 |
ГЛАВА 6 |
ляют их в специально предназначенное для этого место. Этот специальный компонент и называется корневой панелью (root pane). Все компоненты своего пользовательского интерфейса вы будете добавлять именно в корневую панель.
Корневая панель используется вовсе не для того, чтобы все запутать — у нее есть вполне определенное и очень важное назначение, что и позволяет компонентам Swing комфортно чувствовать себя в тяжеловесном контейнере. Прежде всего, она добавляет в контейнеры свойство «глубины» — возможность не только размещать компоненты одним над другим, но и при необходимости менять их местами, увеличивать или уменьшать глубину расположения компонентов. Такая возможность в первую очередь востребована многодокументными приложениями Swing (окна которых представляют собой обычные легковесные компоненты, которые необходимо размещать друг над другом), а также выпадающими меню и всплывающими подсказками. Далее, корневая панель позволяет разместить строку меню Swing и предлагает еще несколько весьма полезных возможностей, позволяя в том числе ускорить процесс прорисовки компонентов, потому что благодаря ей управление этим процессом сразу передается в библиотеку Swing и класс JComponent.
Вообще, корневая панель — это несколько определенным образом расположенных панелей, каждая из которых исполняет определенную роль1. Обсудив каждую часть корневой панели по отдельности, мы сможем увидеть, какие функции она выполняет в целом. Давайте начнем с самого начала.
Многослойная панель JLayeredPane
В основании корневой панели лежит так называемая многослойная панель JLayeredPane. Она занимает все доступное пространство контейнера, и именно в этой панели затем располагаются все остальные части корневой панели, в том числе и все компоненты вашего пользовательского интерфейса. Вполне можно сказать, что это — самая важная часть корневой панели.
Многослойная панель используется для добавления в контейнер свойства глубины (depth), иногда еще говорят, что многослойная панель упорядочивает компоненты в соответствии с их порядком в стопке (Z-order). То есть многослойная панель позволяет организовать в контейнере новое, третье, измерение, вдоль которого располагаются слои (layers) компонента. В обычном контейнере расположение компонента определяется прямоугольником, который показывает, какую часть контейнера занимает компонент. При добавлении компонента в многослойную панель необходимо указать не только прямоугольник, занимаемый компонентом, но и слой, в котором он будет располагаться. Слой в многослойной панели определяется целым числом — слой находится тем выше, чем больше определяющее его число. Окончательно прояснит ситуацию небольшая иллюстрация (рис. 6.1).
1
10
20
30
Рис. 6.1. Слои в многослойной панели
1 Если выражаться языком шаблонов проектирования, можно сказать, что корневая панель — это компоновщик (composite).