Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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

115

public class KeyBindingTest extends JFrame { public KeyBindingTest() {

super("KeyBindingTest");

//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);

//настраиваем карты команд и входных событий для

//корневой панели приложения

InputMap im = getRootPane().getInputMap();

ActionMap am = getRootPane().getActionMap();

//срабатывает при отпускании сочетания Ctrl+A im.put(KeyStroke.getKeyStroke(

KeyEvent.VK_A,

KeyEvent.CTRL_MASK, true), "Action");

//срабатывает при печати буквы 'Я' im.put(KeyStroke.getKeyStroke('Я'), "Action"); am.put("Action", new AnAction());

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

setSize(200, 200); setVisible(true);

}

// класс нашей команды

class AnAction extends AbstractAction { public void actionPerformed(ActionEvent e) {

System.out.println("OK");

}

}

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

new Runnable() {

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

}

}

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

116

ГЛАВА 5

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

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

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

Система передачи фокуса ввода

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

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

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

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

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

117

Настройка системы передачи фокуса

Если не вдаваться в подробности реализации системы передачи фокуса ввода, можно сказать, что она исключительно прозрачна и проста. Для любого компонента, унаследованного от класса 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 { public FocusKeysTest() {

super("FocusKeysTest");

//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);

//добавляем пару кнопок

setLayout(new FlowLayout()); // особая кнопка

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

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

//настроим клавиши перехода фокуса

HashSet<AWTKeyStroke> set = new HashSet<AWTKeyStroke>(); set.add(AWTKeyStroke.getAWTKeyStroke(

'Q', KeyEvent.CTRL_MASK)); button.setFocusTraversalKeys(

KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);

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

setSize(200, 200); setVisible(true);

}

118

ГЛАВА 5

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

new Runnable() {

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

}

}

В данном примере мы добавляем в окно две кнопки, для одной из которых специально настраиваем клавиши передачи фокуса ввода следующему компоненту. Для этого необходимо поместить в множество Set (класс множеств Set является абстрактным, у него есть несколько подклассов, мы использовали HashSet) клавиатурные сокращения, при нажатии которых будет происходить передача фокуса. Клавиатурные сокращения для системы передачи фокуса создаются статическими методами класса AWTKeyStroke3. В примере мы создаем сочетание Ctrl+Q, добавляем его в множество, и передаем множество компоненту, указывая константой FORWARD_TRAVERSAL_KEYS, что это сочетание будет использоваться для передачи фокуса следующему компоненту (клавиши для остальных ситуаций при этом не меняются). Запустив программу, вы убедитесь, что теперь фокус ввода передается от первой кнопки ко второй только новым сочетанием клавиш, в то время как вторая кнопка исправно реагирует на нажатие клавиши Tab.

Теперь, когда мы разобрались, как настраивать клавиши, используемые для передачи фокуса ввода от одного компонента к другому, интересно будет узнать, как система передачи фокуса ввода определяет, какому компоненту будет передан фокус. Этим заведуют подклассы абстрактного класса FocusTraversalPolicy. При срабатывании сочетания клавиш, отвечающего за передачу фокуса ввода, текущий подкласс FocusTraversalPolicy определяет, какой именно компонент получит фокус ввода следующим4. Создать собственный алгоритм нетрудно, однако AWT и Swing предоставляют несколько стандартных алгоритмов передачи фокуса ввода, которых вполне хватает для большинства ситуаций (табл. 5.2).

Таблица 5.2. Стандартные алгоритмы передачи фокуса

Имя класса

Предназначение

ContainerOrderFocusTraversalPolicy

Данный

алгоритм имитирует использовавшийся

 

в прежних версиях JDK переход фокуса ввода со-

 

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

 

тейнер. Первый добавленный в контейнер компо-

 

нент получает фокус при выводе окна на экран, фо-

 

кус переходит к компоненту, добавленному вторым

 

и т. д.

 

DefaultFocusTraversalPolicy

Работает

аналогично предыдущему алгоритму,

 

и к тому же взаимодействует с помощниками (peer)

 

компонентов, что расширяет его возможности по

 

работе с тяжеловесными компонентами. По умол-

 

чанию используется в приложениях AWT. В данный

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

тами Swing

3Это полный аналог класса KeyStroke. С версии JDK 1.4 все функции класса KeyStroke перешли

кAWTKeyStroke, а KeyStroke просто наследует от него.

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

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

119

Таблица 5.2 (продолжение)

 

 

 

Имя класса

Предназначение

 

 

SortingFocusTraversalPolicy

Предоставляется библиотекой Swing. Позволяет

 

организовать передачу фокуса ввода согласно не-

 

которому признаку, который определяет, какие ком-

 

поненты получают фокус раньше, а какие позже.

 

Для работы требует объект Comparator, обеспечи-

 

вающий сравнение компонентов. Может исполь-

 

зоваться для получения весьма интересных спе-

 

циализированных алгоритмов передачи фокуса, так

 

как написать объект сортировки Comparator куда

 

проще, чем создавать алгоритм передачи фокуса

 

«с нуля»

LayoutFocusTraversalPolicy

Расширяет предыдущий алгоритм, передавая

 

ему для сортировки компонентов объект Layout-

 

Comparator. Последний получает список компо-

 

нентов контейнера и выясняет, как они располага-

 

ются на экране. Это позволяет передавать фокус

 

ввода именно в том порядке, в котором располо-

 

жены компоненты перед глазами пользователя.

 

Учитывает особенности локализованных про-

 

грамм (в том числе письмо справа налево). По

 

умолчанию в окнах Swing используется именно

 

этот алгоритм

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

//FocusPolicyTest.java

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

import java.awt.*; import java.awt.event.*;

public class FocusPolicyTest extends JFrame { public FocusPolicyTest() {

super("FocusPolicyTest");

//при закрытии окна выход setDefaultCloseOperation(EXIT_ON_CLOSE);

//добавляем три кнопки

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

//добавляем эту кнопку второй, но она будет ниже

//двух других кнопок

Источник: https://studfile.net/preview/15935977/