WWW.DISS.SELUK.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА
(Авторефераты, диссертации, методички, учебные программы, монографии)

 

Pages:     | 1 |   ...   | 2 | 3 || 5 |

«Swing ЭФФЕКТНЫЕ ПОЛЬЗОВАТЕЛЬСКИЕ ИНТЕРФЕЙСЫ Java Foundation Classes Москва • Санкт-Петербург • Нижний Новгород • Воронеж • Новосибирск • Ростов-на-Дону • Екатеринбург • Самара • Киев • Харьков • ...»

-- [ Страница 4 ] --

Создатели Swing решили настроить механизм проверки корректности получше. Прежде всего, в класс JComponent был добавлен метод isValidateRoot(), проверяющий, «останавливается» ли на данном компоненте процесс проверки корректности в том случае, если корректность проверялась для его потомков. Другими словами, компонент, возвращающий в данном методе значение true, гарантирует, что проверку корректности вполне можно остановить на нем, не проверяя корректность его предков, и этого будет достаточно для приведения интерфейса в нужное состояние. Практически все компоненты используют значение, возвращаемое этим методом по умолчанию (а оно равно false), и, таким образом, говорят, что проверка корректности потомков на них не останавливается, ее следует продолжать вверх по иерархии компонентов-предков. В Swing немного исключений из этого правила — к наиболее важным относятся панель прокрутки (JScrollPane) и корневая панель (JRootPane) 30. Их методы isValidateRoot() возвращают true. Здесь все логично — при изменении размеров содержимого панели прокрутки остальной интерфейс и компоненты не затрагиваются, необходимо только обновить ее саму, в частности, положение и размер полос прокрутки. Корневая панель, как мы узнаем в главе 4, служит для размещения всех остальных компонентов в контейнере высшего уровня, и, как правило, проверка корректности вполне может останавливаться на ней (хотя корневую панель можно использовать в своих целях, на практике это обычно никогда не требуется). Остается выяснить, как Swing обеспечивает проверку корректности 31.

Для поддержки усовершенствованного механизма проверки корректности в класс JComponent был добавлен новый метод revalidate(). Его можно безопасно вызывать в многозадачном окружении — перед началом работы revalidate() проверяет, был ли он вызван из потока рассылки событий, и, если это не так, помещает с помощью метода invokeLater() в очередь событий задание, вызывающее метод revalidate() уже из нужного ему потока выполнения. Далее происходит следующее: revalidate() вызывает определенный в базовом классе Container метод invalidate(), который проводит проверку корректности для компонента и его потомков, а затем вызывает метод addInvalidComponent() из класса RepaintManager. Последний проверяет, есть ли среди предков компонента такой, чей метод isValidateRoot() возвращает true. Если такой компонент не найден, метод заканчивает свою работу 32, в противном случае он ищет контейнер высшего уровня (окно или апплет), в котором размещен переданный ему компонент. Найденный контейнер высшего уровня RepaintManager помещает в специальный ассоциативный массив, где ему сопоставляется список контейнеров, чьи методы isValidateRoot() возвращают true. Для всех потомков таких контейнеров нужно будет провести проверку корректности. После этого RepaintManager остается поместить в очередь событий задание, при исполнении которого для всех находящихся в списке контейнеров и их потомков будет проведена проверка корректности. При этом RepaintManager старается максимально оптимизировать свою работу: для проверки корректности компонентов используется то же самое задание (одно на каждый контейнер высшего уровня), что и для перерисовки. Так что при выполнении этого задания перед вызовом paintDirtyRegions() вызывается метод validateInvalidComponents(), проводящий проверку корректности всех компонентов, собранных в массиве. Учитывая, что проверка корректности и перерисовка в большинстве случаев проводятся вместе, одна за другой, такой подход позволяет сэкономить память и не загромождать очередь событий.

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

Есть и другие подобные компоненты, например, разделяемая панель JSplitPane.

Как мы видим, компонентов с нестандартной проверкой корректности в Swing немного, стоило ли начинать весь «сыр-бор» с оптимизацией проверки корректности всего из-за пары компонентов? Создатели Swing утверждают, что инструменты измерения производительности показывали значительное снижение скорости работы при обычной проверке корректности панелей прокрутки, а такие панели есть в любом более или менее большом приложении. Очевидно, причины для оптимизации были на самом деле веские.

Как правило, такой компонент (корневая панель) всегда находится, и проверка корректности методом revalidate() проходит успешно. Впрочем, если вы разместили компонент Swing вне корневой панели и хотите провести проверку корректности его предков, вызовите доставшийся любому компоненту Swing в наследство от AWT метод validate().

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

Класс DebugGraphics — это тонкая оболочка вокруг стандартного класса рисования Graphics. Всю работу по рисованию, как и прежде, выполняет Graphics, a DebugGraphics по вашему желанию замедляет графические операции и выделяет их специальным цветом (который потом стирается) или выводит о них диагностические сообщения 33. Можно использовать DebugGraphics и напрямую, создав объект этого класса и передав в его конструктор ссылку на стандартный объект Graphics, после чего производить рисование полученным объектом. Указать при этом, какой способ отладки графики вы предпочитаете, позволяет метод setDebugGraphicsOptions(). Однако базовый класс JComponent имеет встроенную поддержку класса DebugGraphics, и чтобы включить для компонента Swing режим отладки графики, надо всего лишь указать способ отладки, вызвав тот же метод setDebugGraphicsOptions(), но на этот раз уже для самого компонента. После этого компонент и все его потомки будут использовать для рисования объект DebugGraphics. В качестве параметра методу setDebugGraphicsOptions() передается целое число, представляющее собой одну из констант, определенных в классе DebugGraphics, или комбинацию этих констант, составленную с помощью операции логического «ИЛИ» (таблица 3.1).

';

Таблица 3.1. Параметры метода setDebugGraphicsOptions() Константа из класса DebugGraphics Действие Давайте рассмотрим небольшой пример, который наглядно продемонстрирует возможности отладки графики:

// DebugPainting.java // Демонстрация возможностей отладки графики в Swing import java.awt.*;

import javax.swing.*;

public class DebugPainting extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

RepaintManager.currentManager(null).setDoubleBufferingEnabled(false);

pc.setDebugGraphicsOptions(DebugGraphics.LOG_OPTION | DebugGraphics.FLASH_OPTION);

На языке шаблонов проектирования класс DebugGraphics представляет собой не что иное, как типичный декоратор.

// компонент, который что-то рисует class PaintingComponent extends JPanel { public static void main(String[] args) { В примере мы создаем небольшое окно, в панель содержимого которого добавляем собственный компонент, унаследованный от класса JPanel. Интересно, что если бы мы унаследовали компонент обычным образом, непосредственно от класса JComponent, система отладки графики с ним работать бы не стала. Связано это с тем, что у базового класса библиотеки нет собственного UI-представителя — ведь это абстрактный класс, его нельзя добавить в окно, и UI-представитель ему просто не нужен.

В обычных программах это не причиняет неудобств, однако система отладки графики отказывается работать с компонентом, у которого нет UI-представителя. Эта небольшая проблема имеет два решения: можно написать для своего компонента, даже для такого простого, как наш, UIпредставителя, который, используя ресурсы своего базового класса ComponentUI, реально ничего делать не будет, но мы еще не добрались до подробного обсуждения UI-представителей. Поэтому в примере применено второе решение: мы унаследовали компонент от класса JPanel, на котором тоже можно спокойно рисовать (сам он ничего не рисует), к тому же у него есть свой UI-представитель.

Далее мы, так как в примере будет использоваться отладка с условием FLASH_OPTION, полностью отключаем двойную буферизацию уже известным нам методом класса RepaintManager() (почти то же самое можно было бы сделать, вызвав метод setDoubleBuffered(false) для корневой панели нашего окна). Для нашего компонента мы включаем два режима отладки: вывод диагностических сообщений и выделение цветом, для этого константы класса DebugGraphics объединяются операцией логического «ИЛИ». Напоследок идет небольшая настройка режима выделения цветом (мы увеличиваем количество мерцаний и задержку, чтобы лучше видеть процесс рисования). Остается запустить приложение и посмотреть на результат.

Диагностические сообщения, выводимые в режиме отладки графики на консоль (хотя можно выводить их в любой поток вывода), имеют простой формат:

Graphics(Nl-N2) Операция: Параметры Здесь N1 — номер используемого в данный момент объекта DebugGraphics (каждый раз при создании объекта DebugGraphics он нумеруется), а N2 — код режима отладки. Значение N2 в нашем примере равно 3 — это именно то число, которое мы передаем в метод setDebugGraphicsOptions() (оно получается при логическом объединении констант). Оно дает возможность узнать, какие операции отладки используются в данный момент. Далее следует краткое текстовое описание графической операции (которое не слишком то информативно и фактически повторяет название вызываемого метода) и список параметров, переданных вызванному методу. Данный пример может быть полезен не только в качестве наглядного пособия по использованию встроенной системы отладки графики, но и как пример работы системы прорисовки Swing. После запуска закройте область нашего окна другим окном, потом откройте ее и посмотрите, как перерисовывается графика. Если была закрыта только часть окна приложения, то заново прорисуется только закрытая часть, хотя диагностические сообщения показывают, что вызываются все операции прорисовки компонента PaintingComponent.

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

Клавиатурные сокращения Одной из самых неприятных проблем AWT в прежних версиях JDK было полное отсутствие в ней толковой поддержки клавиатуры. Вкупе с безобразной системой обработки событий это еще больше усиливало раздражение от работы с AWT и нападки на ее создателей. При создании Swing одной из приоритетных целей было создание продуманной и универсальной системы клавиатурных сокращений (keyboard bindings, или keyboard shortcuts). В большинстве современных оконных систем клавиатурные сокращения используются весьма широко, и не зря: возможность совершить какоелибо частое или сложное действие путем нажатия двух-трех клавиш очень удобно и ценится пользователями.

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

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

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

Создать объект класса Keystroke позволяют определенные в нем перегруженные версии статического метода getKeyStroke() 34. Когда вы вызываете этот метод для создания объекта Keystroke, он прежде всего проверяет, не содержится ли объект с такой же информацией в кэше, и при положительном ответе возвращает вам кэшированный объект. Так как объекты Keystroke всего лишь хранят информацию о клавиатурных сокращениях и сами не выполняют никаких действий, использование одного и того же объекта в разных местах программы безопасно и, кроме того, позволяет реально экономить память и время на инициализацию.

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

Одновременно с выполнением своих основных обязанностей класс Keystroke еще и функционирует как фабрика по созданию своих экземпляров.

Первая такая карта, хранящаяся в классе JComponent, называется картой входных событий (input map). Это экземпляр класса InputMap; он представляет собой отображение некоторых входных событий, которые воспринимает компонент, на объекты, отвечающие за действие компонента в случае возникновения этих событий. В идеале в такой карте должны храниться все входные события, воспринимаемые компонентом, но пока Swing использует ее лишь для хранения клавиатурных сокращений, поддерживаемых компонентом. Все клавиатурные сокращения, которые должен обрабатывать компонент, необходимо поместить в эту карту, для этого в классе InputMap определен метод put(KeyStroke, Object). В качестве второго параметра метода чаще всего указывают строку, идентифицирующую действие, которое должно вызывать помещаемое в карту клавиатурное сокращение (впрочем, вторым параметром может быть любой объект). Обычно новое клавиатурное сокращение добавляют в уже имеющуюся в компоненте карту входных событий, которую можно получить методом getInputMap(). Однако можно и полностью изменить реакцию компонента на действия пользователя с клавиатуры, установив свою карту методом setInputMap(). Именно так поступают UI-представители при настройке компонента, это позволяет им «одним махом» придать компоненту поведение, свойственное ему на той платформе, которую они представляют.

На самом деле у компонентов не одна карта входных событий, а целых три. Каждая их них хранит клавиатурные сокращения, поддерживаемые компонентом в одном из трех состояний (таблица 3.2).

Таблица 3.2. Состояния компонента при обработке клавиатурных сокращений WHEN_ANCESTOR_OF_FOCUSED_COMPONENT Фокусом ввода обладает один из потомков компонента. Ни WHEN_IN_FOCUSED_WINDOW Событие пришло к какому-то компоненту, находящемуся в Получить карту входных событий для определенного состояния позволяет все тот же метод getInputMap(), точнее его перегруженная версия, которой необходимо передать константу, идентифицирующую это состояние. Вызов этого метода без параметров возвращает карту для состояния WHEN_FOCUSED. Таким образом вы можете зарегистрировать клавиатурные сокращения для любого состояния вашего компонента. Чаще других используются состояния WHEN_FOCUSED (когда сокращение срабатывает только в том случае, если компонент обладает фокусом ввода: к примеру, выделение текста в текстовом поле имеет смысл только в этом состоянии) и WHEN_IN_FOCUSED_WINDOW (когда для инициирования какого-либо глобального действия в программе сокращение должно сработать, в каком бы месте вашего окна пользователь его ни нажал).

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

В классе InputMap определен метод setParent(), который позволяет задать для карты ее предка.

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

Карта второго типа, хранящаяся в классе JComponent, называется картой команд (action map). Это отображение некоторых объектов-ключей на классы, реализующие интерфейс Action (унаследованный от интерфейса ActionListener). Интерфейс Action предназначен для объектов, которые выполняют какое-то действие в компоненте. Мы подробнее обсудим этот интерфейс в главе 7, сейчас важно знать лишь то, что в нем есть метод actionPerformed(), который и вызывается для выполнения команды. Карты команд, реализованные классом Action Map, содержат все команды, поддерживаемые компонентом. Чаще всего в карте команд в качестве ключей хранятся те же строки, что и в карте входных событий, и, таким образом, карты входных событий и карты команд связывают сообщения от клавиатуры и действия, которые они совершают. Аналогично картам входных событий, карты команд могут иметь предков. Для получения карты команд используется пара методов get/set.

«Почему же клавиатурные сокращения не связаны с командами напрямую, в одной карте?» — спросите вы. Здесь несколько причин. Во-первых, клавиатурные сокращения различны для трех состояний компонента, в то время как в карте команд просто хранятся все поддерживаемые компонентом команды. Во-вторых, так проще изменять поведение компонента: вы можете заменить клавиатурное сокращение, не изменяя команды, которое оно выполняет, а можете изменить команду, оставив клавиатурное сокращение тем же самым. Ну и, в-третьих, как мы уже отмечали, карты InputMap и ActionMap позволяют использовать предков, что может быть очень полезным. Не стоит забывать и о том, что у раздельных карт есть «запас прочности»: в будущем Swing может использовать их не только для клавиатурных сокращений.

Методы поддержки клавиатурных сокращений Итак, как уже отмечалось, поддержка клавиатурных сокращений обеспечивается базовым классом библиотеки JComponent, который переопределяет метод processKeyEvent() и заменяет стандартный механизм своей цепочкой обработки событий от клавиатуры. Действие происходит следующим образом.

1. Приходит событие от клавиатуры. Система обработки событий AWT вызывает для текущего (обладающего фокусом ввода) компонента метод processKeyEvent().

2. Если дело происходит в компоненте библиотеки Swing, вызывается метод processKeyEvent() класса JComponent. Прежде всего он вызывает базовую версию метода super.processKeyEvent(), которая распределяет события по слушателям (если, конечно, таковые были зарегистрированы для компонента). После этого JComponent смотрит, не было ли событие полностью обработано (то есть не указал ли на это какой-либо из слушателей, вызвав метод consume()). Если событие обработано, работа этого события от клавиатуры завершается. Таким образом, приоритет на обработку события находится у слушателей.

Кстати, все события от клавиатуры всегда добираются до метода processKeyEvent() класса JComponent, даже если в компоненте не были зарегистрированы слушатели, а значит, события соответствующего типа не добавлены в маску компонента (маскирование событий мы обсуждали в главе 2). Следит за этим конструктор класса JComponent, в котором в маску компонента добавляются события от клавиатуры (методом enableEvents()).

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

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

4. Далее, если ни слушатели, ни сам компонент так и не проявили интереса к событию, приходит пора проверить, не является ли это событие клавиатурным сокращением, зарегистрированным в компоненте. Для этого прежде всего запрашивается специальный внутренний класс KeyboardState, определенный в классе JComponent. Функция его проста — он отслеживает нажатие любых сочетаний клавиш и позволяет избежать срабатывания некоторого сочетания клавиш в том случае, если оно было нажато в другом окне или в другом приложении, а отпущено уже в нашем приложении. Обслуживаются лишь те сочетания клавиш, которые нажимаются пользователем в окнах нашего приложения. Если никаких препятствий для обслуживания сочетания клавиш не выявлено, управление передается методу processKeyBindings().

5. Метод processKeyBindings() приступает непосредственно к поиску зарегистрированных в компоненте клавиатурных сокращений и проверке того, не соответствует ли пришедшее от клавиатуры событие одному из этих сокращений. Помогают ему в этом класс Keystroke, а также карты входных событий и команд.

6. Метод processKeyBinding() проверяет, не зарегистрировано ли в карте входных событий для состояния WHEN_FOCUSED пришедшее клавиатурное сокращение. Если оно там есть, метод получает из карты объект, соответствующий сокращению, и с его помощью пытается найти в карте команд соответствующую команду. Если такая команда есть, она выполняется, и работа завершается. (Кстати, вы можете переопределить метод processKeyBinding(), если захотите реализовать в своем компоненте особенный способ обработки сокращения.) 7. Если сокращение не было обработано, начинается опрос родительских компонентов. Для каждого из них также вызывается метод processKeyBinding(), но на этот раз ему указывают, что компонент находится в состоянии WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, так что поиск производится в соответствующей карте входных событий.

8. Если и после этого сокращение не было обработано, предпринимается последняя попытка найти желающего его обработать. К работе привлекается класс KeyboardManager. Данный класс используется в библиотеке Swing в единичном экземпляре 35 для хранения всех контейнеров высшего уровня и применяемых в них строк меню, которые автоматически регистрируются в нем при своем создании. Он заведует распределением клавиатурных сокращений для состояния WHEN_IN_FOCUSED_WINDOW, а также обеспечивает поддержку клавиатуры для меню. Итак, метод processKeyBindings() вызовом метода KeyboardManager.getCurrentManager() получает текущий объект и вызывает метод fireKeyboardAction().

Метод fireKeyboardAction() проводит окончательное распределение клавиатурного сокращения. Базовый класс библиотеки JComponent заботится о том, чтобы все компоненты, у которых в карте WHEN_IN_FOCUSED_WINDOW есть клавиатурные сокращения, автоматически регистрировались в классе KeyboardManager. Поэтому в классе KeyboardManager уже имеется список компонентов, способных обработать такое сокращение 36 (это позволяет избежать ненужного и дорогого «ворошения» всех содержащихся в окне компонентов, которых там может быть много). Для них он вызывает все тот же метод processKeyBinding(), указывая ему, что поиск надо вести в карте для состояния WHEN_IN_FOCUSED_WINDOW. Если сокращение так и не было обработано, настает пора строк меню JMenuBar. Для каждой зарегистрированной в классе KeyboardManager строки меню также вызывается метод processKeyBinding(), и на этом поиск владельца клавиатурного сокращения заканчивается.

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

// KeyBindingTest.java // Пример использования клавитурных сокращений import javax.swing.*;

import java.awt.event.*;

public class KeyBindingTest extends JFrame { public KeyBindingTest() { setDefaultCloseOperation(EXIT_ON_CLOSE);

// настраиваем карты команд и входных событий для корневой панели приложения im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK, true), "Action");

im.put(KeyStroke.getKeyStroke('Я'), "Action");

class AnAction extends AbstractAction { public static void main(String[] args) { Все, что мы сказали о классе RepaintManager, относится и к классу KeyboardManager. Это не классический одиночка, а одиночка в смысле используемого экземпляра. Как видно, довольно популярная модель у создателей Swing.

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

В данном примере мы создаем маленькое окно и настраиваем клавиатурные сокращения для корневой панели этого окна (в любом контейнере высшего уровня Swing есть корневая панель). В нашем примере используются два сокращения: первое срабатывает при отпускании сочетания клавиш Ctrl+A (именно при отпускании, вы увидите это, запустив пример, такие сокращения позволяет создавать класс Keystroke), а второе показывает возможность работы и с кириллицей. Правда, кириллические буквы будут распознаваться только для событий типа KEY_TYPED (печать символа), потому что в событиях KEY_PRESSED и KEY_RELEASED участвуют только символы с виртуальными кодами, определенными в классе KeyEvent, а кириллических символов там нет.

Поэтому при создании объектов Keystroke для кириллических символов используйте те методы getKeyStroke(), которые создают клавиатурное сокращение для событий KEY_TYPED. Один из таких методов мы и задействовали в примере. Эта проблема очень неприятно сказывается на клавиатурных сокращениях, используемых в кнопках и меню Swing, когда клавиши быстрого доступа отказываются работать с русской раскладкой клавиатуры 37. Мы обсудим это в главе 7, посвященной кнопкам.

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

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

Система передачи фокуса ввода Фокус ввода — довольно простое определение. Компонент, обладающий фокусом ввода, первым получает все события от клавиатуры. Эти события не обязательно остаются в самом компоненте (как мы уже видели, клавиатурные сокращения обрабатываются даже в тех случаях, когда событие от клавиатуры произошло в другом компоненте), но тем не менее у компонента, обладающего фокусом ввода, всегда есть шанс обработать нажатие клавиш первым. Фокус ввода передается от компонента к компоненту различными способами: когда пользователь щелкает на другом компоненте мышью (если тот обрабатывает щелчки мыши) или нажимает специальные клавиши (чаще всего клавишу Tab или ее сочетания с управляющими клавишами). Система передачи фокуса ввода, встроенная в любую схему пользовательского интерфейса, определяет, какие компоненты могут обладать фокусом ввода и порядок его передачи.

Несмотря на всю простоту, в графических библиотеках Java с передачей фокуса были постоянные проблемы. «Проштрафилась», как всегда, библиотека AWT, а точнее ее первый поспешный выпуск, в котором поддержка передачи фокуса ввода между компонентами возлагалась на операционную систему. Для первого выпуска JDK все было еще не так плохо, потому что там существовали только тяжеловесные компоненты, и операционная система с грехом пополам справлялась с ними, все-таки они были полностью под ее управлением. Однако появившиеся затем легковесные компоненты оказались без внимания операционной системы, и передать им фокус ввода можно было только грубой силой, обрабатывая событие от клавиатуры и выясняя, подходит ли оно для передачи фокуса.

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

Кстати, не только с русской, но и с любой другой, отличной от английской.

Однако решение Swing было половинчатым, потому что оно плохо сочеталось с компонентами других библиотек, будь то AWT или библиотека компонентов от стороннего производителя, и практически не взаимодействовало с системой передачи фокуса операционной платформы. В итоге оно работало, но вызывало большое количество нареканий разработчиков. Компоненты неожиданно теряли фокус ввода там, где он должен был быть, не работала программная передача фокуса, иногда нельзя было определить, на каком компоненте находится фокус, одним словом, ошибок было множество. Разработчики Java решили избавиться от них, предложив в выпуске пакета JDK 1.4 новую систему передачи фокуса ввода. Теперь вся власть и полномочия по передаче фокуса ввода перешли к классу библиотеки AWT с названием KeyboardFocusManager. Любые сочетания клавиш и события, подходящие для передачи фокуса ввода, приходят прежде всего к нему. Происходит это на самом низком уровне обработки событий базового компонента всех графических библиотек Java Component, что позволяет унифицировать процесс передачи фокуса для любых компонентов, независимо от того, тяжеловесные ли они или легковесные и к какой библиотеке принадлежат. Теперь любой унаследованный от Component класс (а значит, и все компоненты библиотеки Swing) находится под контролем новой системы фокуса ввода и класса KeyboardFocusManager. Прежде чем перейти к обсуждению деталей реализации новой системы передачи фокуса ввода, давайте посмотрим, как она функционирует с точки зрения ее пользователя, то есть программиста, который настраивает поведение своего компонента.

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

Клавиатурные сокращения, служащие для передачи фокуса ввода, хранятся во множествах Set, передать их в компонент можно методом setFocusTraversalKeys(). По умолчанию для передачи фокуса используются клавиши Tab и Shift+Tab для передачи фокуса к следующему и предыдущему компоненту соответственно. После настройки ваш компонент окажется под надежной опекой класса KeyboardFocusManager, который позаботится о том, чтобы фокус ввода вовремя оказывался у вашего компонента и покидал его только при нажатии тех клавиш, что вы указали. Давайте рассмотрим небольшой пример:

// FocusKeysTest.java // Настройка клавиш перехода фокуса ввода import javax.swing.*;

import java.awt.*;

import java.awt.event.KeyEvent;

import java.util.HashSet;

public class FocusKeysTest extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

set.add(AWTKeyStroke.getAWTKeyStroke('Q', KeyEvent.CTRL_MASK));

button.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);

getContentPane().setLayout(new FlowLayout());

getContentPane().add(new JButton("Обычная"));

JButton button = new JButton("Особая");

public static void main(String[] args) { В данном примере мы добавляем в окно две кнопки, для одной из которых специально настраиваем клавиши передачи фокуса ввода следующему компоненту. Для этого необходимо поместить в множество Set (класс множеств Set является абстрактным, у него есть несколько подклассов, мы использовали HashSet) клавиатурные сокращения, при нажатии которых будет происходить передача фокуса. Клавиатурные сокращения для системы передачи фокуса создаются статическими методами класса AWTKeyStroke 38. В примере мы создаем сочетание Ctrl+Q, добавляем его в множество и передаем множество компоненту, указывая константой FORWARD_TRAVERSAL_KEYS, что это сочетание будет использоваться для передачи фокуса следующему компоненту (клавиши для остальных ситуаций при этом не меняются). Запустив программу, вы убедитесь, что теперь фокус ввода передается от первой кнопки ко второй только новым сочетанием клавиш, в то время как вторая кнопка исправно реагирует на нажатие клавиши Tab.

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

Учитывая, что в контейнерах чаще всего использовались несколько вложенных менеджеров расположения (эту технику мы рассмотрим в главе 5), часто получалось, что фокус передавался не совсем так, как предполагало расположение компонентов. Новая система передачи фокуса ввода позволяет гибко выбирать компоненты, получающие фокус. Этим заведуют подклассы абстрактного класса FocusTraversalPolicy. При срабатывании сочетания клавиш, отвечающего за передачу фокуса ввода, текущий подкласс FocusTraversalPolicy определяет, какой именно компонент получит фокус ввода следующим 39. Создать собственный алгоритм нетрудно, однако AWT и Swing предоставляют несколько стандартных алгоритмов передачи фокуса ввода, которых вполне хватает для большинства ситуаций (таблица 3.3).

Таблица 3.3. Стандартные алгоритмы передачи фокуса ContainerOrderFocusTraversalPolicy Данный алгоритм имитирует использовавшийся в прежних версиях JDK DefaultFocusTraversalPolicy Работает аналогично предыдущему алгоритму и к тому же взаимодействует SortingFocusTraversalPolicy Предоставляется библиотекой Swing. Позволяет организовать передачу LayoutFocusTraversalPolicy Расширяет предыдущий алгоритм, передавая ему для сортировки Помимо того, что описанные алгоритмы определяют очередность получения компонентами фокуса ввода, они еще выполняют много мелкой работы, точно выясняя, подходит ли компонент для получения фокуса, и, учитывая при этом расположение, видимость компонента и его личное желание обладать фокусом. При написании собственного алгоритма обо всем этом придется думать самому, так что при такой необходимости лучше использовать класс SortingFocusTraversalPolicy, позволяющий сортировать компоненты в отдельном объекте. Напоследок давайте рассмотрим небольшой пример, иллюстрирующий различия описанных алгоритмов:

// FocusPolicyTest.java // Различные алгоритмы передачи фокуса ввода import javax.swing.*;

import java.awt.ContainerOrderFocusTraversalPolicy;

import java.awt.event.*;

Это полный аналог класса Keystroke. С версии JDK 1.4 все функции класса Keystroke перешли к AWTKeyStroke, a Keystroke просто наследует от него.

Класс FocusTraversalPolicy — это не что иное, как стратегия, или подключаемый алгоритм.

public class FocusPolicyTest extends JFrame { public FocusPolicyTest() { setDefaultCloseOperation(EXIT_ON_CLOSE);

getContentPane().add(new JButton("Левая"), "West");

button.addActionListener( new ActionListener() { getContentPane().add(new JButton("Правая"), "East");

public static void main(String[] args) { Здесь мы создаем небольшое окно с тремя кнопками, причем добавляются они в панель содержимого таким образом, чтобы порядок расположения их в окне не совпадал с порядком добавления их в контейнер. По умолчанию в панели содержимого используется полярное расположение, его мы и учли для получения такого эффекта, добавив вторую по счету кнопку в нижнюю часть окна. К ней добавляется слушатель событий, обрабатывающий нажатие кнопки и меняющий при этом алгоритм передачи фокуса в окне.

Запустив программу с примером, вы сможете оценить работу двух различных алгоритмов передачи фокуса ввода. Сначала действует алгоритм LayoutFocusTraversalPolicy, и фокус ввода передается так, как располагаются кнопки в окне. Сменив алгоритм на ContainerOrderFocusTraversalPolicy, вы увидите, во-первых, что теперь фокус переходит так, как мы добавляли кнопки в окно (и с точки зрения пользователя это довольно неудобно), а во-вторых, что на некоторое время фокус исчезает в каких-то невидимых компонентах. Это проблема текущих выпусков JDK, потому что компоненты Swing еще не переписаны под новую систему передачи фокуса, а по умолчанию новая система считает, что любой компонент нуждается в фокусе ввода, хотя корневая панель в нем не нуждается.

Именно в корневой панели и «застревает» ненадолго фокус ввода. Алгоритм LayoutFocusTraversalPolicy справляется с этой проблемой, потому что дополнительно проверяет старые свойства, связанные с фокусом ввода, определенные в классе JComponent еще до появления новой системы. Поэтому лучше не использовать (по крайней мере, до исправления в новых выпусках JDK) в приложениях Swing алгоритмы передачи фокуса AWT.

Новые возможности Помимо того, что новая система передачи фокуса ввода избавилась от имевшихся проблем и теперь позволяет гибко настраивать процесс передачи фокуса, она также привнесла несколько дополнительных возможностей, прежде отсутствовавших в Java. Прежде всего, стоит отметить понятие цикла передачи фокуса (focus cycle) и его корня (focus cycle root). Фокус ввода передается только в пределах цикла передачи фокуса, который представляет собой набор из некоторых компонентов. Компоненты находятся в контейнере, который называется корнем цикла. По умолчанию в качестве корня используются только контейнеры высшего уровня, и все добавляемые в них компоненты оказываются в одном цикле, однако корнем можно сделать любой контейнер, что может быть полезным для очень больших контейнеров, напичканных компонентами. Для перехода из одного цикла передачи фокуса в другой применяется специальное множество клавиатурных сокращений, его позволяет задать класс KeyboardFocusManager. Чтобы заставить контейнер вести себя как корень цикла, необходимо переопределить его метод isFocusCycleRoot() так, чтобы он возвращал значение true. Тем не менее, в обычных приложениях с обычными пользовательскими интерфейсами создание дополнительных циклов передачи фокуса требуется редко, поскольку лишь «добавляет пользователям головной боли».

Далее стоит сказать о новых возможностях окон Java, унаследованных от класса Window. Раньше любое окно Java могло обладать фокусом ввода (это означает, что фокус ввода может находиться у компонентов, располагающихся в данном окне), что не позволяло создавать, например, окна-палитры с набором кнопок-инструментов, которые не должны получать фокус ввода, оставляя его у главного окна приложения. Теперь, благодаря усилиям класса KeyboardFocusManager, у окон Java появилась пара новых событий, а также возможность отказываться от фокуса ввода. Для отключения возможности получения фокуса ввода в окне вы можете использовать метод setFocusableWindowState(false). После этого компоненты, расположенные в окне, не будут получать сообщений от клавиатуры. Новые события (описанные в интерфейсе слушателя WindowFocusListener) позволяют различить активизацию окна (в общем случае это означает, что пользователь использовал окно последним) и получение окном фокуса ввода (что может произойти только в окне, которое соглашается обладать фокусом ввода). Интересно, что события, сообщающие о получении фокуса ввода, теперь полностью создаются внутри Java классом KeyboardFocusManager, а не приходят из операционной системы. Также благодаря тому, что система передачи фокуса ввода теперь полностью написана на Java, появилась возможность программно управлять передачей фокуса и вдобавок получать исчерпывающую информацию о состоянии системы и компонентах, обладающих фокусом.

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

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

Взгляд изнутри — класс KeyboardFocusManager В заключение мы обсудим работу новой системы передачи фокуса, взглянув на нее не со стороны программиста-клиента, а заглянув внутрь нее. Мы уже упоминали, что поддержка передачи фокуса ввода начинается в базовом классе всех графических компонентов Component и обеспечивается классом KeyboardFocusManager.

Так оно и есть.

1. После извлечения из очереди EventQueue нового события поток обработки событий EventDispatchThread вызывает метод dispatchEvent() класса Component. Последний начинает свою работу и еще до вызова методов, производящих рассылку события по слушателям, передает событие методу retargetFocusEvent() класса KeyboardFocusManager.

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

После рассылки всех нужных событий управление возвращается в dispatchEvent().

3. Далее вызывается метод dispatchEvent() класса KeyboardFocusManager. Он тщательно отслеживает все происходящие в системе события и соответствующим образом изменяет состояние системы передачи фокуса ввода. На данном этапе предварительно обрабатываются события WINDOW_ACTIVATED и FOCUS_GAINED, сообщающие об активизации окон и получении компонентами фокуса. При этом меняются активное окно системы и глобальный владелец фокуса ввода, благодаря слежению за этими событиями KeyboardFocusManager имеет достоверную информацию о состоянии системы. Здесь же синтезируются события WINDOW_DEACTIVATED, FOCUS_LOST и WINDOW_GAINED_FOCUS, которые не поддерживаются операционной системой напрямую. Это позволяет обеспечить систему передачи фокуса и слушателей дополнительной информацией и вносит ясность в последовательность возникновения событий и получения компонентами фокуса.

4. Если класс KeyboardFocusManager не сообщает об успешной обработке события, продолжается работа класса Component. Предпринимается попытка включить в работу механизм передачи фокуса операционной системы, если таковой имеется. В случае успеха событие считается обработанным. В противном случае для событий от клавиатуры KeyEvent вызывается метод processKeyEvent() все того же класса KeyboardFocusManager.

5. Метод processKeyEvent() проверяет, подходит ли пришедшее от клавиатуры событие для передачи фокуса ввода (например, события типа KEY_TYPED для передачи фокуса ввода считаются непригодными) и производит поиск соответствующего клавиатурного сокращения во множествах клавиш передачи фокуса текущего компонента. Если в одном из множеств сочетание найдено, выполняется передача фокуса к следующему или предыдущему компоненту либо к следующему или предыдущему циклу передачи фокуса в зависимости от того, в каком множестве было найдено клавиатурное сокращение. Компонент, которому передается фокус ввода, определяет алгоритм FocusTraversalPolicy. Если сокращение было найдено, событие считается обработанным, после чего оно больше не передается Несмотря на кажущуюся простоту, мы видим, что классу KeyboardFocusManager приходится плотно взаимодействовать с системой AWT, тщательно отслеживать все происходящие в ней события и самому синтезировать некоторые из них, избавляясь от недостатков, характерных для взаимодействия с различными операционными системами. Вы можете заменить используемый в AWT экземпляр этого класса своим собственным, взяв, таким образом, всю ответственность по поддержке передачи фокуса ввода в свои руки, однако это довольно сложно. Стандартный класс KeyboardFocusManager предоставляет достаточно гибкие способы управления процессом передачи фокуса, начиная от настройки клавиш передачи фокуса и заканчивая алгоритмами выбора компонентов. Этих способов должно хватить в большинстве ситуаций.

Остается сказать, что KeyboardFocusManager полностью поглощает те события от клавиатуры, что соответствуют клавишам передачи фокуса ввода, и вы не сможете увидеть их ни в методе processKeyEvent(), ни в слушателях событий от клавиатуры. Иногда это может быть неудобно, так что класс KeyboardFocusManager предоставляет два дополнительных интерфейса специально для таких ситуаций: интерфейс KeyEventDispatcher позволяет обработать событие еще до того, как его коснется система передачи фокуса, а интерфейс KeyEventPostProcessor дает возможность обработать те события, которые так и не были обработаны в приложении. Зарегистрировать эти интерфейсы, так чтобы они начали получать сообщения о событиях с клавиатуры, можно все в том же классе KeyboardFocusManager.

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

Очень полезным свойством всех без исключения компонентов Swing является возможность показывать всплывающие подсказки (tool tips) при небольшой задержке указателя мыши над компонентом. Поддержка Swing всплывающих подсказок также обеспечивается базовым классом JComponent. В нем определен метод setToolTipText(), в который вы передаете текст подсказки. При передаче в данный метод текста JComponent регистрирует компонент во вспомогательном классе ToolTipManager. Последний представляет собой одиночку и централизованно управляет процессом вывода подсказок в системе. При регистрации компонентов он присоединяется к ним в качестве слушателя событий от мыши и при появлении указателя мыши на компоненте запускает таймер, следящий за временем, в течение которого указатель мыши остается на компоненте неподвижным, и, таким образом, определяет, когда следует вывести подсказку. Подробнее о всплывающих подсказках мы узнаем в главе 6.

Наконец, в арсенале класса JComponent есть еще одно оружие — клиентские свойства (client properties), весьма удачная находка создателей Swing. Эти свойства практически аналогичны свойствам JavaBeans, только они не требуют наличия в классе компонента поля, хранящего значение самого свойства, и пары методов get/set. Вместо этого значение свойства хранится в специальном ассоциативном массиве, а для его изменения и получения используется пара методов putClientProperty() и getClientProperty(). Клиентские свойства, как и свойства JavaBeans, являются привязанными и позволяют настраивать и изменять компонент без его переписывания и наследования от него. Иногда это может быть очень полезно, например, при появлении свойств, которые еще не совсем готовы или чересчур усложняют компонент. В Swing клиентские свойства используются очень широко, особенно для настройки внутренней работы компонентов и их взаимодействия, а также для наделения компонента новыми свойствами, которые предположительно (но не обязательно) станут затем обычными свойствами JavaBeans, а пока просто используются для изучения реакции разработчиков на нововведение (как это было, например, с плавающими панелями инструментов). Клиентские свойства весьма удобны при разработке новых компонентов, позволяя сократить количество вспомогательных методов и полей, их также удобно задействовать при добавлении к уже существующим компонентам новых возможностей, когда наследование от компонентов излишне громоздко. Впрочем, злоупотреблять ими не следует, и если у вас появляется с десяток свойств, реализованных в виде клиентских, стоит подумать о создании нового компонента с обычными свойствами JavaBeans.

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

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

Мы говорили, что все компоненты библиотеки Swing являются легковесными и не требуют поддержки операционной системы — это верно, но только не для контейнеров высшего уровня. Они представляют собой тяжеловесные компоненты (точнее, контейнеры) и являются исключением из общего правила. Ничего страшного в этом исключении нет: достаточно вспомнить, что легковесные компоненты (к ним относятся все компоненты Swing) являются просто областью экрана некоторого тяжеловесного контейнера, который любезно предоставляет им свое пространство. Как раз контейнеры высшего уровня и предоставляют остальным компонентам Swing пространство экрана для работы, а также следят, чтобы они вовремя получали сообщения операционной системы о перерисовке и возникновении событий (как мы знаем, операционная система понятия не имеет о существовании легковесных компонентов). На самом деле оказывается, что контейнеры высшего уровня Swing — это самые незатейливые компоненты библиотеки. Они просто наследуют от соответствующих классов AWT (Frame, Window, Dialog и Applet), а также, чтобы смягчить разницу между контейнерами AWT и компонентами Swing, застилают свое пространство специальной «соломкой» в виде так называемой корневой панели, которая обеспечивает легковесные компоненты некоторыми возможностями, отсутствующими в контейнерах AWT (например, специальным местом для строки меню Swing, о которой AWT знать не знает). На этом работа контейнеров высшего уровня Swing заканчивается. Можно попробовать разместить компоненты Swing напрямую в окнах AWT, но только в результате получится не слишком работоспособный интерфейс, так как корневая панель предоставляет компонентам Swing весьма важные услуги. Поэтому, прежде чем перейти к рассмотрению контейнеров высшего уровня Swing (а они очень просты), мы поподробнее рассмотрим корневую панель и ее роль в создании интерфейса и функционировании некоторых компонентов.

Корневая панель JRootPane Каждый раз, когда вы создаете новый контейнер высшего уровня, будь то обычное окно, диалоговое окно или апплет, в конструкторе этого контейнера создается особый компонент — экземпляр класса JRootPane, который добавляется в контейнер, причем так, чтобы занять все доступное в контейнере место. Более того, все контейнеры высшего уровня Swing следят за тем, чтобы другие компоненты не смогли «пробраться» за пределы этого специального компонента: они переопределяют метод add(), предназначенный для добавления компонентов, и при вызове этого метода возбуждают ошибку выполнения программы 40, принуждая вас добавлять свои компоненты только в специальный компонент. Этот специальный компонент и называется корневой панелью (root pane). Все компоненты своего пользовательского интерфейса вы будете добавлять именно в корневую панель.

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

Такая возможность, прежде всего, востребована многодокументными приложениями Swing (окна которых представляют собой обычные легковесные компоненты, которые необходимо размещать друг над другом), а также выпадающими меню и всплывающими подсказками. Далее, корневая панель позволяет разместить строку меню Swing и предлагает еще несколько весьма полезных Сколько не напоминай, что контейнеры высшего уровня Swing не позволяют напрямую добавлять в себя компоненты, рано или поздно такая ошибка возникает (тем более что в остальные контейнеры, например в панели, добавление происходит обычным образом — методом add()). Это уже стало отличительным знаком Swing, и к этому надо просто привыкнуть.

возможностей, позволяя в том числе ускорить процесс прорисовки компонентов, потому что благодаря ей управление этим процессом сразу передается в библиотеку Swing и класс JComponent.

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

Многослойная панель JLayeredPane В основании корневой панели лежит так называемая многослойная панель JLayeredPane. Она занимает все доступное пространство контейнера, и именно в этой панели располагаются затем все остальные части корневой панели, в том числе и все компоненты вашего пользовательского интерфейса. Вполне можно сказать, что это — самая важная часть корневой панели.

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

Как видно, многослойную панель можно представить себе как своего рода «слоеный пирог» — она словно состоит из нескольких контейнеров, расположенных друг на друге. На самом деле контейнер всего один, просто внутренние механизмы многослойной панели следят за тем, чтобы компоненты располагались соответственно своим слоям. Компоненты, находящиеся в более высоком слое, всегда будут перекрывать компоненты из более низких слоев (впрочем, это верно только для ситуации, когда компоненты реально перекрывают друг друга, в противном случае то, что они находятся в разных слоях, внешне никак не проявляется). Слоев может быть столько, сколько целых чисел вы сможете придумать, главное здесь то, что слои с большими номерами располагаются в стопке выше слоев с меньшими номерами. Кроме слоя, который определяет, как высоко будут находиться все компоненты, находящиеся в этом слое, многослойная панель также позволяет задать позицию компонента в слое. Позиция компонента позволяет указать, как будут располагаться компоненты из одного и того же слоя. Вполне может быть, что два компонента, находящиеся в одном и том же слое, перекрывают друг друга. В таких ситуациях в дело и вступает позиция компонента — она определяет, какой из компонентов будет в стопке выше. Позиция задается несколько иначе, чем слой — она определяется целым числом от нуля и больше, но выше в стопке будет располагаться компонент с меньшим номером. Объяснить это очень просто: позиция соответствует очередности добавления компонентов в контейнер — первый добавленный компонент всегда имеет нулевую позицию, второй первую и т. д. На рисунке 4.2 показано, как располагаются компоненты согласно своим позициям.

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

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

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

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

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

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

Чтобы избежать путаницы, многослойная панель определяет несколько стандартных слоев, которые и используются всеми компонентами Swing, что позволяет обеспечить правильную работу всех механизмов многослойной панели. Давайте посмотрим, какие стандартные слои имеются в Swing (рисунок 4.3).

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

Таблица 4.1. Предназначение стандартных слоев многослойной панели Название слоя Предназначение Default Этот слой используется для всех обычных компонентов, которые вы добавляете в контейнер.

В нем же располагаются внутренние окна многодокументных приложений Palette Слой предназначен для размещения так называемых палитр, или окон с набором инструментов, которые обычно перекрывают остальные элементы интерфейса. Создавать такие окна позволяет панель JDesktopPane, которая размещает их как раз в этом слое Modal Судя по названию, разработчики планировали использовать этот слой для размещения легковесных модальных диалоговых окон. Однако такие диалоговые окна пока не реализованы, так что этот слой в Swing в настоящее время не используется Popup Наиболее часто используемый слой, служащий для размещения всплывающих меню и Drag Самый верхний в стопке спой. Предназначен для обслуживания операций перетаскивания Чтобы окончательно познакомиться с многослойной панелью, рассмотрим небольшой пример. Он покажет, как добавлять компоненты в различные слои и как слои располагаются друг над другом в стопке:

// LayeredTest.java // Возможности многослойной панели import javax.swing.*;

import java.awt.*;

public class LayeredTest extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

// класс, позволяющий рисовать два типа фигур с текстом class Figure extends JComponent { public static void main(String[] args) { В данном примере мы создаем небольшое окно JFrame, указываем ему, что при закрытии окна необходимо завершить программу (с помощью метода setDefaultCloseOperation(), о котором мы вскоре узнаем подробнее), и добавляем в многослойную панель несколько компонентов Figure. Чтобы получить многослойную панель в любом контейнере высшего уровня Swing, достаточно вызвать метод getLayeredPane(). Вспомогательный класс Figure наследует от базового класса JComponent и позволяет различными цветами рисовать фигуры двух типов (круги и прямоугольники), вводя поверх фигур надписи. Параметры для прорисовки фигур задаются в конструкторе класса. Заметьте, что компоненты Figure не закрашивают цвет фона (свойство непрозрачности установлено в false, для этого мы в конструкторе воспользовались методом setOpaque()), это позволяет им лучше «просвечивать» друг сквозь друга. Здесь мы создаем три фигуры разного цвета (два круга и прямоугольник) и добавляем круг в слой POPUP_LAYER, а прямоугольники — в слой PALETTE_LAYER. Заметьте, что при добавлении компонентов приходится указывать их абсолютные экранные координаты, потому что в многослойной панели обычные менеджеры расположения не работают. В нашем примере мы для простоты сразу указали координаты фигур, однако в реальных приложениях лучше тщательно рассчитать размеры компонентов, учитывая размеры шрифтов и другие графические параметры, потому что на разных платформах все может выглядеть по-разному.

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

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

Панель содержимого Панель содержимого (content pane) — следующая часть корневой панели, служащая для размещения компонентов пользовательского интерфейса вашей программы. Она занимает большую часть пространства многослойной панели (за исключением того места, что занимает строка меню). Чтобы панель содержимого не закрывала добавляемые впоследствии в окно компоненты, многослойная панель размещает ее в специальном очень низком слое с названием. FRAME_CONTENT_LAYER, с номером -30000. Обычные компоненты размещать в этом слое не стоит, потому что тогда вы их вряд ли увидите, а для панели содержимого этот слой подходит в самый раз, потому что она служит всего лишь для размещения других компонентов (при добавлении в панель содержимого все компоненты оказываются в слое DEFAULT_LAYER). Именно в панель содержимого следует добавлять все компоненты вашего пользовательского интерфейса, это основное отличие контейнеров высшего уровня Swing от их собратьев из AWT. Попытка добавить компоненты напрямую в контейнер высшего уровня Swing приведет к ошибке, так что следующий фрагмент кода имеет смысл выгравировать и повесить у себя перед глазами:

getContentPane().add(Bаш_Kомпонент) А про простой вызов метода add() лучше забыть. Впрочем, панель содержимого — это всего лишь экземпляр обычной панели JPanel, в которой для совместимости с окнами AWT устанавливается полярное расположение BorderLayout, поскольку считается, что в окнах по умолчанию должно использоваться полярное расположение (мы увидим в главе 5, что это действительно удобно). Никто не запрещает поместить все ваши компоненты в отдельную панель с удобным вам расположением, а затем сделать ее панелью содержимого вашего окна. Это дело вкуса. Давайте рассмотрим небольшой пример:

// ContentPaneAdd.java // Замена панели содержимого import javax.swing.*;

public class ContentPaneAdd extends JFrame { public ContentPaneAdd() { setDefaultCloseOperation(EXIT_ON_CLOSE);

JPanel contents = new JPanel();

contents.add(new JButton("Один"));

contents.add(new JButton("Два"));

public static void main(String[] args) { В примере мы создаем небольшое окно и панель с двумя кнопками, которую затем методом setContentPane() делаем панелью содержимого нашего окна. Так мы используем более простой вызов add() и вместо полярного расположения задействуем предлагаемый по умолчанию в панелях JPanel менеджер FlowLayout. Тем не менее, разница невелика и выбор того или иного подхода зависит от ваших пристрастий. Вообще говоря, панель содержимого не представляет собой ничего хитрого, надо лишь помнить, что компоненты добавляются именно в нее.

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

Однако стандартные окна AWT, напрямую связанные с операционной системой, могут размещать только строки меню AWT и не знают о легковесных меню Swing. Здесь нам помогает корневая панель. Она выделяет подстроку меню специальное место и предоставляет метод setJMenuBar(), позволяющий вам установить в контейнере новую строку меню 42 (строка меню может использоваться в окне JFrame, диалоговом окне JDialog и апплете JApplet).

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

Прозрачная панель Прозрачная панель (glass pane) — это последний составляющий элемент корневой панели.

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

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

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

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

имеется специальное место для строк меню, а все остальное пространство находится в вашем распоряжении.

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

// HelpSystemDemo.java // Как прозрачная панель может помочь в создании системы помощи import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

public class HelpSystemDemo extends JFrame { private JButton button1, button2, help;

private HelpSystem hs = new HelpSystem();

private InterceptPane ip = new InterceptPane();

private ImageIcon helpIcon = new ImageIcon("Help.gif");

public HelpSystemDemo() { setDefaultCloseOperation(EXIT_ON_CLOSE);

JPanel contents = new JPanel();

contents.add(button1);

contents.add(button2);

help.addActionListener(new ActionListener() { getRootPane().setCursor(getToolkit().createCustomCursor(helpIcon.getImage(),

contents.add(help);

// компонент, перехватывающий события class InterceptPane extends JComponent {

JPanel contents = new JPanel();

contents.add(b1);

contents.add(b2);

public static void main(String[] args) { Мы добавляем в окно две кнопки JButton и задаем для каждой из них текст подсказки, что автоматически приводит к регистрации кнопок в классе ToolTipManager. Для первой кнопки мы используем простой текст, который появится в виде однострочной подсказки, шрифт и цвет которой зависят от текущих внешнего вида и расположения. Но вот для второй кнопки мы используем уже знакомый нам HTML-текст (помните, что начинать HTML-строку следует с символов ), и здесь у вас, так же как и в случае с надписями, просто безграничные возможности. Вы можете задействовать любые возможности HTML версии 3.2 для создания краткой, но мощной системы помощи, действующей прямо «на лету». В примере мы применили стиль заголовка и маркированный список — эти элементы могут использоваться для перечисления возможностей компонента. С другой стороны, стоит помнить о том, что большие и красочные подсказки, неожиданно загораживающие экран при остановке указателя мыши на любом компоненте, могут раздражать пользователя — подробные описания функций приложения стоит оставлять для общей системы помощи.

Помимо метода setToolTipText(), позволяющего задать для вашего компонента подсказку, у вас есть еще пара методов, способных повлиять на ее вывод, — это методы getToolTipLocation() и getToolTipText(MouseEvent), определенные в базовом классе JComponent. Правда, пользоваться ими довольно неудобно, потому что соответствующего метода set нет, и для получения нужного эффекта их приходиться переопределять, что мы и сделали в нашем примере, создав анонимный подкласс кнопки JButton. Метод getToolTipLocation() позволяет указать классу ToolTipManager, в каком месте экрана следует выводить подсказку относительно компонента, для которого подсказка выводится.

Можно указывать различные места для подсказки в зависимости от событий Mouse Event, по которому подсказка выводится. В примере мы указали, что подсказка должна выводиться на расстоянии 10 пикселов по осям X и Y от верхнего левого угла нашей кнопки (можно указывать и отрицательные расстояния, это будет означать, что вы хотите вывести подсказку не ниже компонента, как обычно, а выше). Правда, действует описанный механизм, только если подсказка выводится как легковесный компонент, то есть когда ей хватает места в пределах окна нашего приложения. В противном случае класс ToolTipManager сам решает, где удобнее ее разместить. Полученный эффект вы сможете наблюдать, запустив программу с примером: подсказка первой кнопки будет выводиться в разных местах, в зависимости от положения указателя мыши, а для второй кнопки она всегда будет появляться на указанном нами расстоянии (если ей хватит места). Метод getToolTipText() позволяет выводить для одного и того же компонента различные подсказки в зависимости от того, где пользователь задержал указатель мыши (координаты этого места позволяет узнать объект MouseEvent). Данный метод часто задействуется сложными компонентами Swing, такими как списки или таблицы, состоящими из многих элементов. С его помощью они обеспечивают вывод собственных подсказок для каждого элемента. По умолчанию он возвращает для любой точки текст, заданный методом setToolTipText(), но переопределив этот метод, вы сможете изменить подобное поведение. В примере мы используем подсказку по умолчанию только для верхней части кнопки (высотой в 10 пикселов), а для нижней части возвращаем собственный текст.

Механизм работы всплывающих подсказок в Swing довольно прост. Как мы уже отмечали, при вызове метода setToolTipText() компонент регистрирует себя в классе ToolTipManager. Последний начинает следить за перемещениями мыши в компоненте (причем для всех компонентов используется один и тот же слушатель событий от мыши, поскольку для каждого события можно узнать его источник — компонент, к которому оно относится). Когда указатель мыши входит в область компонента (вызывается метод слушателя mouseEntered()), класс ToolTipManager запускает таймер, при срабатывании которого (если при этом не происходит движение мыши, иначе таймер запускается заново) на экран выводится подсказка. Подсказка создается в два этапа. Собственно компонентподсказка JToolTip создается методом createToolTip(), имеющимся в любом компоненте Swing.

Другой компонент (тот, что будет «всплывать» над остальными компонентами, в нем размещается подсказка JToolTip) создается с помощью вспомогательного класса PopupFactory. Этот класс определяет тип компонента (легковесный или тяжеловесный) и размещает его в нужном месте экрана (в слое POPUP_LAYER многослойной панели или в новом окне без рамки JWindow). Общую картину иллюстрирует рисунок 6.1.

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

У вас есть возможность управлять только некоторыми аспектами работы класса ToolTipManager.

Прежде всего вы можете переопределить метод createToolTip() в вашем компоненте, если вам понадобится предоставить для него какую-то особенную подсказку, но делать это слишком часто не рекомендуется. Лучше, если у приложения имеется единый продуманный облик и подсказки в него органично вписываются. Если вас не устраивают подсказки, предоставляемые используемым вашим приложением внешним видом, лучше всего написать для них новый UI-представитель. С другой стороны, имеются стандартные варианты настройки подсказок, и мы их сейчас рассмотрим.

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

// ToolTipsTuning.java // Настройка подсказок import javax.swing.*;

public class ToolTipsTuning extends JFrame { public ToolTipsTuning() { setDefaultCloseOperation(EXIT_ON_CLOSE);

JPanel contents = new JPanel();

contents.add(b1);

contents.add(b2);

ToolTipManager ttm = ToolTipManager.sharedInstance();

public static void main(String[] args) { Как и в предыдущем примере, мы добавляем в окно две кнопки, для которых включаем вывод подсказок. Далее с помощью экземпляра класса ToolTipManager (нетрудно видеть, что он представляет собой классического одиночку, экземпляр которого возвращает метод sharedInstance()) мы производим настройку подсказок нашего приложения. Доступные свойства перечислены в таблице 6.2.

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

Таблица 6.2. Свойства класса ToolTipManager lightWeightPopupEnabled Рекомендует классу ToolTipManager использовать для подсказок легковесные Если значение равно false, подсказки размещаются либо в тяжеловесных компонентах initialDelay Задержка (в миллисекундах) между остановкой указателя мыши на компоненте и dismissDelay Время, в течение которого подсказка остается на экране (если только не происходит reshowDelay Время, в течение которого действует «режим подсказок». Если пользователь, прочитав В общем и целом, класс ToolTipManager довольно неплохо справляется со своими обязанностями, но без изъяна не обошлось: он не поддерживает компоненты произвольной формы. Как мы знаем, легковесные компоненты могут быть прозрачными и иметь самые фантастичные формы, а для проверки нахождения указателя мыши на таком компоненте используется метод contains(), который позволяет, например, кнопкам JButton обрабатывать щелчки мыши. К сожалению, класс ToolTipManager не использует метод contains(), а рассчитывает, когда указатель мыши окажется на компоненте на основе его прямоугольных размеров. Вероятно, это артефакт прежних версий Swing, вынужденных укладываться в рамки не слишком производительных виртуальных машин Java. В результате может получиться довольно неприятный эффект: пользователь располагает указатель мыши рядом с компонентом произвольной формы, не касаясь его, а подсказка все равно появляется.

В таких ситуациях ничего не остается, как вывести подсказку самому (заменить экземпляр класса ToolTipManager нельзя, да и переписывать его сложно) — создать подсказку JToolTip, установить для нее текст методом setTipText(), получить всплывающее окно из класса PopupFactory 58 методом getPopup() и вывести его на экран тогда, когда указатель мыши останавливается на компоненте.

Конечно, такая сложная замена простого метода setToolTipText() не радует, но пока приходится с этим мириться. Возможно, в будущих версиях Swing ситуация будет исправлена.

Рамки Рамки (borders) библиотеки Swing позволяют визуально упорядочить пользовательский интерфейс, разделяя компоненты по их функциям и назначению. Также довольно часто они используются в качестве «украшения» для того или иного компонента библиотеки, и нередко именно рамка определяет особый колорит компонента. Если вы вспомните внешний вид кнопок JButton, специальные границы текстовых полей или панелей прокрутки, то убедитесь, что основу внешнего вида таких компонентов иногда составляет необычная рамка.

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

Поддержка рамок обеспечивается рисующими механизмами базового класса JComponent (детали мы исследовали в главе 3), так что для любого компонента Swing вы можете использовать нужную вам рамку. Рамка хранится в качестве свойства border, сменить или получить ее позволяют методы get/set.

Обязанности рамок в Swing описаны в интерфейсе Border из пакета javax.swing.border, в этом же пакете находится впечатляющее количество стандартных рамок. Давайте рассмотрим пример и ознакомимся с ними.

// Borders.java // Рамки Swing Этот класс, так же как и ToolTipManager, представляет собой одиночку, экземпляр его позволяет получить метод PopupFactory.getSharedInstance(). Для создания всплывающих окон лучше использовать именно класс PopupFactory, потому что он кэширует создаваемые окна, ускоряя их работу.

import javax.swing.*;

import javax.swing.border.*;

import java.awt.*;

public class Borders extends JFrame { setDefaultCloseOperation(EXIT_ON_CLOSE);

JPanel contents = new JPanel(new GridLayout(3, 2, 5, 5));

contents.add(createPanel(new TitledBorder("Рамка с заголовком"), "TitledBorder"));

contents.add(createPanel(new EtchedBorder(), "EtchedBorder"));

contents.add(createPanel(new BevelBorder(BevelBorder.LOWERED), "BevelBorder"));

contents.add(createPanel(new SoftBevelBorder(BevelBorder.RAISED), "SoftBevelBorder"));

contents.add(createPanel(new LineBorder(Color.green, 5), "LineBorder"));

contents.add(createPanel(new MatteBorder(new ImageIcon("matte.gif")), "MatteBorder"));

// метод создает панель с рамкой и надписью private JPanel createPanel(Border b, String text) { panel.setBorder(new CompoundBorder(b, new EmptyBorder(30, 30, 30, 30)));

public static void main(String[] args) { В примере мы создаем окно, для панели содержимого которого выбираем табличное расположение с шестью ячейками; именно шесть наиболее употребительных рамок содержит пакет javax.swing.border.

В ячейки табличного расположения мы добавляем панели с установленными рамками и надписью JLabel, которая помогает разобраться, какой класс позволяет создать ту или иную рамку. При этом используется вспомогательный метод createPanel(), который создает новую панель с полярным расположением, в центр ее добавляет надпись и устанавливает нужную рамку Border. Заметьте, что метод createPanel() «украшает» панели с помощью сразу трех рамок: собственно панель получает в качестве рамки объект CompoundBorder, позволяющий совместить две рамки в одну. Внешней рамкой является та, которая передается в метод в качестве параметра, а внутренняя рамка для всех панелей одна — это рамка EmptyBorder, оставляющая между границей панели и ее содержимым пустое пространство, чтобы надпись с названием рамки было проще читать. Запустив программу с примером, вы сможете насладиться разнообразием рамок Swing, и это только начало: каждая из рассмотренных нами шести рамок имеет дополнительные варианты оформления.

Когда вы будете выбирать рамку для своего приложения, вам сможет помочь таблица 6.3 с их краткими характеристиками.

Таблица 6.3. Стандартные рамки Swing Название рамки Назначение TitledBorder Позволяет совместить любую рамку и текст, описывающий то, что в этой рамке находится.

При этом текст может находиться на любой стороне рамки и иметь различные варианты LineBorder Одна из самых простых, но наиболее часто используемых рамок. Рисует рамку линией определенного цвета, толщину линии можно выбрать по вкусу, позволяет скруглять углы EmptyBorder Отличный помощник в создании выверенных пользовательских интерфейсов, позволяет окружить компонент пустыми полями со всех четырех сторон. Мы уже использовали эту BevelBorder Позволяет создать объемную рамку, выпуклую или вогнутую, по желанию можно настроить цвета, требуемые для получения объемных эффектов. Имеет «сильный» визуальный SoftBevelBorder Эта рамка аналогична предыдущей (унаследована от нее), обладает теми же свойствами, но EtchedBorder Рамка с тиснением (в стиле чеканки по металлу) выглядит скромно, но эффектно, поэтому CompoundBorder Позволяет совместить две рамки и избавляет от утомительного совмещения панелей.

Вкладывая такие рамки друг в друга, можно совместить их произвольное количество MatteBorder Очень полезная и мощная рамка, позволяющая использовать узор из значков Icon. С ее помощью легко создавать очень необычные рамки. Она часто избавляет от необходимости С помощью стандартных рамок Swing можно добиться практически любого эффекта и в плане организации пользовательского интерфейса, и в плане оформления компонентов. Однако с рамками следует быть осторожным: слишком большое их количество утомляет и раздражает пользователя, интерфейс кажется пестрым и тяжелым для восприятия. Лучше ограничиться одной-двумя рамками для четкого разделения компонентов, выполняющих разные функции или собирающих данные разного предназначения, и никогда не следует вкладывать рамки друг в друга, если только это не относится к рамке EmptyВorder. Для организации компонентов интерфейса лучше всего использовать рамку TitledBorder, для оформления компонентов хорошо подходят неброские рамки LineBorder и EtchedBorder. Если вам понадобится особенно красочная рамка, вы можете обратиться к MatteBorder.

Всегда стоит держать под рукой рамку с пустым пространством EmptyBorder. Она не вносит в интерфейс излишней пестроты и позволяет хорошо организовать его. В главе 5 мы кратко рассмотрели рекомендации по созданию пользовательских интерфейсов для внешнего вида Metal, проще всего выполнить которые можно именно с помощью рамок EmptyBorder.

Фабрика BorderFactory Мы уже узнали, как напрямую создавать рамки из пакета javax.swing.border. Оказывается, есть еще один способ создания рамки — использовать фабрику javax.swing.BorderFactory. Это класс с набором статических методов, создающих тот или иной тип рамки. Он позволяет отказаться от импорта пакета javax.swing.border, а также по мере возможности кэширует создаваемые объекты Border. На самом деле, компоненты Swing прорисовываются поочередно, один за другим (как вы помните, запрос на прорисовку приходит из очереди событий), и это позволяет использовать один и тот же объект Border для разных компонентов. Правда, успешно кэшировать можно лишь рамки без состояния, к которым относятся объемные рамки BevelBorder и SoftBevelBorder, а также рамка EtchedBorder. Остальные рамки отличаются друг от друга цветами, надписями и другими параметрами и создаются по одной на компонент. Давайте убедимся, что использовать класс BorderFactory несложно.

// UsingBorderFactory.java // Фабрика рамок BorderFactory import javax.swing.*;



Pages:     | 1 |   ...   | 2 | 3 || 5 |
Похожие работы:

«МИНОБРНАУКИ РОССИИ Государственное образовательное учреждение высшего профессионального образования Поморский государственный университет имени М.В. Ломоносова (ПГУ имени М.В. Ломоносова) Основная образовательная программа высшего профессионального образования Направление подготовки: 060602.65 Медицинская биофизика Квалификация (степень): специалист Форма обучения: очная Архангельск 2011 г. Общие положения. 1. 1.1. Основная образовательная программа (ООП) подготовки специалиста, реализуемая ПГУ...»

«Актис Санкт-Петербург ул. Рубинштейна д. 15/17 оф. 251 (код домофона 21) тел./факс 309-35-00 www.tourworld.ru ЮАР - БОТСВАНА – ЗАМБИЯ По Африке на королевском поезде Фото тур по трем африканским странам Даты начала тура: 6, 13, 18, 27 октября 2014 г. Маршрут: Кейптаун – Поезд Ровос – Крюгер Парк – Водопад Виктория, Замбия Изюминки этой программы: мыс Доброй Надежды, путешествие на поезде Ровос, эксклюзивный лодж в Крюгере и Водопад Виктория ПРОГРАММА ТУРА День 1. Кейптаун Прибытие в Кейптаун,...»

«Муниципальное бюджетное общеобразовательное учреждение средняя общеобразовательная школа им. А. Искандарова д. Ирсаево муниципального района Мишкинский район Республики Башкортостан (МБОУ СОШ им. А. Искандарова д. Ирсаево) УТВЕРЖДАЮ: Директор: _ /А.А. Назмиев/ ПРОГРАММА ВНЕУРОЧНОЙ ДЕЯТЕЛЬНОСТИ (начальное общее образование) Ирсаево-2013 Пояснительная записка Программа внеурочной деятельности младших школьников МБОУ СОШ им. А. Искандарова д. Ирсаево МР Мишкинский район РБ разработана на основе...»

«ГБОУ СОШ №1305 Рабочая программа география 8 класс учитель Ланчикова Н.Ю 2013-2014 уч.год. -2Пояснительная записка Статус документа Исходными документами для составления рабочей программы учебного курса являются: федеральный компонент государственного образовательного стандарта, утвержденный Приказом Минобразования РФ примерные программы, созданные на основе федерального компонента государственного образовательного стандарта; Федеральный перечень учебников, рекомендованных (допущенных) к...»

«III Международная конференция Информационные технологии для Новой школы 23-26 марта 2012 года ПРОГРАММА КОНФЕРЕНЦИИ Сводный график проведения мероприятий РЦОКОиИТ Вторая Санкт-Петербургская (Вознесенский пр., д. 34 а) гимназия (ул. Казанская, д. 27) Время Мероприятия Время Мероприятия 23 марта 2012 (пятница) Регистрация участников 9.30–10.00 Мастер-классы 10.00–12.00 Перерыв 12.00–13.00 Мастер-классы 13.00 – 15.00 Перерыв 15.00 – 15.30 Мастер-классы 15.30 – 17. 24 марта 2012 (суббота)...»

«МИНИСТЕРСТВО СЕЛЬСКОГО ХОЗЯЙСТВА РФ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ ОРЛОВСКИЙ ГОСУДАРСТВЕННЫЙ АГРАРНЫЙ УНИВЕРСИТЕТ Рабочая программа дисциплины (модуля) Диетология (наименование дисциплины (модуля)) Направление подготовки 111801 - Ветеринария (указывается наименование направления подготовки) Квалификация (степень) специалист Форма обучения очная Орел 2013 год 1 Составители: доцент, к.б.н., доцент, Шеховцова Т.А. должность,...»

«Приложение № 8 к Территориальной программе государственных гарантий бесплатного оказания гражданам Российской Федерации медицинской помощи в Красноярском крае на 2014 год и на плановый период 2015 и 2016 годов Перечень лекарственных препаратов, отпускаемых населению в соответствии с перечнем групп населения и категорий заболеваний, в том числе при оказании паллиативной помощи, при амбулаторном лечении которых лекарственные средства и изделия медицинского назначения отпускаются по рецептам...»

«Ростехнологии Государствениая корпорация ростехнологии> ПРИКАЗ Ng ^96 Москва О решениях годового общего собрания акционеров ОАО НИИ ПС На основании статьи 18.1 Федерального закона от 23.11.2007 Ns270-Ф3 О Государственной корпорации Ростехнологии, постановления Правительства Российской Федерации от 17.10.2009 N^831 06 осуществлении Государственной корпорацией Ростехнологии от имени Российской Федерации прав акционера акционерных обществ, акции которых находятся в федеральной собственности и...»

«Государственное бюджетное образовательное учреждение высшего профессионального образования Воронежская государственная медицинская академия имени Н.Н.Бурденко Министерства здравоохранения и социального развития Российской Федерации ОСНОВНАЯ ПРОФЕССИОНАЛЬНАЯ ОБРАЗОВАТЕЛЬНАЯ ПРОГРАММА ПОСЛЕВУЗОВСКОГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ ПО СПЕЦИАЛЬНОСТИ СТОМАТОЛОГИЯ ОБЩЕЙ ПРАКТИКИ (ординатура) Воронеж - 2012 ОДОБРЕНА Ученым Советом ГБОУ ВПО ВГМА им. Н.Н. Бурденко Минздравсоцразвития России 26.04.2012 г....»

«ДОПОЛНИТЕЛЬНАЯ ПРОФЕССИОНАЛЬНАЯ ПРОГРАММА повышения квалификации по направлению профессиональной переподготовки Управление проектами в вузе УЧЕБНЫЙ ПЛАН Цель: формирование компетенций по решению профессиональных задач через организацию работы по проектному принципу. Категория слушателей: специалисты с высшим и средним специальным образованием Срок обучения: 72 часа Форма обучения: очная Режим занятий: 4 - 8 учебных часов в день. Результат: Слушатель, освоивший программу, должен: обладать...»

«Утверждаю Директор ОБОУ СПО Курский педагогический колледж _ О.И. Бондарева Приказ от 05 мая 2014 г. № 115 УЧЕБНЫЙ ПЛАН основной профессиональной образовательной программы среднего профессионального образования областного бюджетного образовательного учреждения среднего профессионального образования Курский педагогический колледж по программе подготовки специалистов среднего звена по специальности среднего профессионального образования 050146 (44.02.02) Преподавание в начальных классах на 2014...»

«Центр исследований гражданского общества и некоммерческого сектора Национального исследовательского университета Высшая школа экономики приглашает исследователей принять участие в летней школе Теоретические и эмпирические исследования социальных инноваций как фактора развития гражданского общества, третьего сектора и волонтерской деятельности В 2014 году летняя школа Центра исследований гражданского общества и некоммерческого сектора НИУ ВШЭ (http://grans.hse.ru) вновь станет площадкой, на...»

«МИНИСТЕРСТВО ТРАНСПОРТА РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное агентство морского и речного транспорта Утверждаю: Руководитель Федерального агентства морского и речного транспорта А.А. Давыденко 2012 г. ПРИМЕРНАЯ ПРОГРАММА Судовой электрик (Правило III/7 МК ПДНВ78 с поправками) Москва 2012 Учебный план программы курса Судовой электрик Цель: подготовка в области профессиональных знаний судовых электриков, в соответствии с требованиями Правила III/7 МК ПДНВ78 с поправками, Раздела A – III/7 и таблицы A...»

«ПРАВИТЕЛЬСТВО ПЕНЗЕНСКОЙ ОБЛАСТИ ПОСТАНОВЛЕНИЕ от 06 марта 2014 года № 145-пП г.Пенза О внесении изменений в государственную программу Пензенской области Развитие образования в Пензенской области на 2014–2020 годы, утвержденную постановлением Правительства Пензенской области от 30.10.2013 № 804-пП (с последующими изменениями) Руководствуясь Законом Пензенской области от 22.12.2005 № 906-ЗПО О Правительстве Пензенской области (с последующими изменениями), Правительство Пензенской области п о с т...»

«1 Программа стратегического развития ДГТУ на 2012–2016 годы деятельности учреждений высшего профессионального образования. Основаниями для реструктуризации деятельности вузов являются положения Болонского и Копенгагенского процессов, современные требования социальноэкономического развития общества, новые концепции управления. Программа стратегического развития ДГТУ реализуется по утвержденному комплексному плану мероприятий, обозначенному в Программе развития университета Инженерное...»

«Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования О М С К И Й Г О С У Д А Р С Т В Е Н Н Ы Й Т Е Х Н И Ч Е С К И Й У Н И В Е даю ОмГТУ гриплинг 20_L5 год РАБОЧАЯ ПРОГРАММА по дисциплине У Р О В Е Н Ь Ж И З Н И. К А Ч Е С Т В О Т Р У Д О В О Й Ж И З Н И (М. 2.02.06) 080100.68 Э коном ика для всех реализуемы х магистерских программ: Э коном ика труда; Э коном ика ф ирм ы и отраслевы х рынков; У правление предприятиями и пром ы ш ленная инф...»

«Негосударственное образовательное учреждение высшего профессионального образования Западно-Уральский институт экономики и права (НОУ ВПО ЗУИЭП) Кафедра истории и гуманитарных дисциплин Т. Ю. Шестова УРАЛЬСКИЙ РЕГИОН: КУЛЬТУРА Учебно-методический комплекс Специальности: 080507. 65 Менеджмент организации Рекомендовано кафедрой Протокол № 5 от 18 июня 2009 г. Зав. кафедрой к. и. н., профессор И. К. Кирьянов Пермь 2009 ББК 28.2.(2-2)Пермь Ш52 Составитель: д. и. н., доцент Т. Ю. Шестова Шестова, Т....»

«CONLIGUS COMPENSATION PLAN 2.0 REWARD PLAN 2.0 CONLIGUS: MP Маркетинг-Партнер (МП) и Член Сообщества Conligus. Спонсор: человек, который представил тебе Сообщество Conligus или подключил тебя. SPONSOR CV Единица объема в Conligus, используется для денежных расчетов твоих комиссионных. Объем оборота в твоей сети конвертируется в Циклы. Один цикл это 300CV/150CV. CYCLE B.E.P Business Expansion Pool – Бизнес-фонд дополнительных бонусов для помощи в развитии бизнеса. L.E.P Leadership Expansion Pool...»

«СОБЫТИЯ КОМПАНИЯ № 14 (341) 2007 ЭМИТЕНТ РОССИЙСКИЙ РЫНОК КОММЕРЧЕСКОЙ 25 НЕДВИЖИМОСТИ ВЫБРАЛ ЛУЧШИХ В Сочи 16 июня 2007 г. состоялось одно из важнейших событий на рынке коммерческой недвижимости — вручение Федеральной премии Commercial Real Estate Awards — 2007 за лучшие проекты в области коммерческой недвижимости. Commercial Real Estate Awards — ежегодная профессиональ- В этот раз организаторы привезли в Россию пул инвестоная премия в области коммерческой недвижимости, учрежден- ров мирового...»

«Председатель Президентского Совета Гильдии аудиторов ИПБР, Генеральный директор Северо-Западного территориального института профессиональных бухгалтеров, генеральный директор аудиторской компании Петро-Балт-Аудит, доктор экономических наук, академик МАРЭ, Член Совета по аудиторской деятельности при Министерстве Финансов Российской Федерации Скобара Вячеслав Владимирович С момента создания т.е. с 2000 года Вячеслав Владимирович Скобара возглавляет Северо-Западный территориальный институт...»






 
2014 www.av.disus.ru - «Бесплатная электронная библиотека - Авторефераты, Диссертации, Монографии, Программы»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.