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

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

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

Рисование в Swing

105

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

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

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

Graphics(N1-N2) Операция: Параметры

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

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

Отладка графики вдохновила создателей некоторых библиотек на встраивание в них на том или ином уровне подобных механизмов. К примеру, в популярном стороннем менеджере расположения MigLayout, о котором мы вкратце расскажем в главе 7, есть визуальная отладка, позволяющая отследить, где располагаются все компоненты и какое именно пространство им выдал менеджер расположения.

Резюме

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

Глава 5. Внутренние винтики Swing

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

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

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

Проверка корректности компонентов

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

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

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

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

Внутренние винтики Swing

107

Чтобы пометить этот факт, метод invalidate() вызывает точно такой же метод контейнера, в котором компонент хранится, и все идет по цепочке до самого верха.

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

Рассмотрим небольшой пример:

//Базовая валидация AWT — при изменении размеров

//или других параметров остается вызвать validate() import java.awt.*;

public class AWTValidateShow extends Frame { private static Button button;

public AWTValidateShow() { setSize(400, 300);

Panel contents = new Panel(); button = new Button("Текст");

Button button2 = new Button("Текст 2"); contents.add(button); contents.add(button2);

add(contents);

}

public static void main(String[] args) throws InterruptedException {

new AWTValidateShow().setVisible(true); Thread.sleep(2000); button.setLabel("Очень длинный текст");

//С этого момента размер поменялся — вызван invalidate()

//можно вызывать validate() в контейнере

Thread.sleep(2000);

//будет заново расположен весь контейнер

//и все его содержимое (кнопка)

button.getParent().validate();

}

}

108

ГЛАВА 5

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

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

Метод Swing: revalidate()

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

Чтобы каким-то образом помечать компоненты, на которых можно остановить проверку корректности, в базовом классе JComponent появился метод isValidateRoot(). Те компоненты, которые утверждают, что все изменения их содержимого не отразятся на их размерах (и значит, на размерах всего окружающего), вернут в этом методе true (к ним относится JScrollPane и корневая панель JRootPane, которая является основой любого окна в Swing). Такие компоненты мы называем корнем валидации. По умолчанию метод возвращает false.

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

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

Теперь понятно, что эффект revalidate() коренным образом отличается от validate(). Если validate() нужно вызывать с умом, выбирая тот контейнер, проверка которого заново расположит все нужные нам компоненты, то revalidate() совершенно безразличен к тому,

Внутренние винтики Swing

109

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

Рассмотрим пример:

//Валидация Swing — большинство компонентов

//позаботятся о себе сами. В остальном метод revalidate()

//позволяет не задумываться о деталях

import javax.swing.*;

public class SwingValidateShow extends JFrame { private static JButton button, newButton;

public SwingValidateShow() { setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setSize(400, 300);

JPanel contents = new JPanel(); button = new JButton("Текст");

JButton button2 = new JButton("Текст 2"); contents.add(button); contents.add(button2);

add(contents);

}

public static void main(String[] args) throws InterruptedException {

SwingUtilities.invokeLater(new Runnable() { public void run() {

new SwingValidateShow().setVisible(true);

}

});

Thread.sleep(2000);

//Кнопка при смене параметра сама вызовет

//revalidate() и мы сразу же увидим изменения

SwingUtilities.invokeLater(new Runnable() { public void run() {

button.setText("Очень длинный текст");

}

});

//при добавлении в контейнер revalidate()

//автоматически не вызывается

110

ГЛАВА 5

SwingUtilities.invokeLater(new Runnable() {

public void run()

{

newButton = new

JButton("Новичок");

button.getParent().add(newButton);

}

});

Thread.sleep(2000);

// revalidate() может быть вызван из любого потока newButton.revalidate();

}

}

В этом примере мы снова используем Swing, а значит должны работать со всеми компонентами из потока рассылки событий. Как и в примере с AWT, создается окно с двумя кнопками. Однако, Swing тут же доказывает свое удобство, автоматически проверяя корректность кнопки при смене ее надписи методом setText(). Затем мы добавляем в панель с кнопками еще одну кнопку, что не приведет к автоматической проверке. Чтобы ее провести, мы вызываем revalidate() для самой кнопки. Это найдет корень валидации (корневую панель нашего окна) и заново расположит все компоненты в ней. Для какого компонента вызывается revalidate(), не так уж и важно.

Сам метод revalidate() может быть безопасно вызван из любого потока — он автоматически заботится о том, чтобы дальнейшие действия происходили в потоке рассылке событий.

На деле пример не так прост, как кажется. Если вам повезет, вы сможете увидеть как новая кнопка появляется в окне сразу же, еще до вызова revalidate(). Секрет тут прост: в таких случаях мы успеваем добавить ее еще до того, как выполнилась задача проверки корректности, запущенная методом setText(), и она «прихватывает» с собой и новый компонент в панели.

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

Проверка корректности в Swing: резюме

Соединяя вместе правила работы классического метода validate() и более изощренного revalidate(), мы получаем следующие выводы:

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

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

При разработке собственных сложных компонентов, не меняющих свои размеры (к примеру, легковесных плавающих диалогов), вам может пригодится метод

Внутренние винтики Swing

111

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

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

Клавиатурные сокращения

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

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

Класс KeyStroke

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

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

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

112

ГЛАВА 5

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

Карты входных событий и команд

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

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

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

Таблица 5.1. Состояния компонента при обработке клавиатурных сокращений

Состояние

Описание

WHEN_FOCUSED

Компонент обладает фокусом ввода,

 

то есть все события прежде всего идут

 

к нему, а затем уже к родительским ком-

 

понентам

WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

Фокусом ввода обладает один из потом-

 

ков компонента. Ни один из потомков

 

событие от клавиатуры не обработал,

 

и оно предлагается самому компоненту

WHEN_IN_FOCUSED_WINDOW

Событиепришлоккакому-токомпонен-

 

ту, находящемуся в том же окне, что

 

и наш компонент. Тот компонент и его

 

предки событие не обработали, и оно

 

по очереди предлагается всем находя-

 

щимся в окне компонентам Swing

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

Внутренние винтики Swing

113

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

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

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

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

Методы поддержки клавиатурных сокращений

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

114

ГЛАВА 5

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

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

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

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

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

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

//KeyBindingTest.java

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

import javax.swing.*; import java.awt.event.*;