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

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

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

Элементы управления

245

создать большое приложение с множеством функций и большую часть этих функций мы реализовали в виде разнообразных элементов управления: простых кнопок JButton, пунктов меню, всплывающих меню и т. д. Некоторые из этих элементов управления могут выполнять одну и ту же работу, но размещаться в разных местах: в диалоговых окнах, в меню, на панелях инструментов. Создав все нужные вам элементы управления, вы можете получить от одного из них модель (методом getModel()), и разделить ее между всеми элементами, выполняющими одно и то же действие (хотя внешний вид и расположение этих элементов управления могут быть разными). Разделение модели не таит в себе черной магии: вы просто передаете ссылку на одну и ту же модель разным элементам, используя метод setModel(). После этого, все элементы управления, имеющие одну и ту же модель, всегда будут находиться в одном и том же состоянии. К примеру, если некоторое действие становится недоступным, вы можете отключить одну из кнопок или сделать то же самое непосредственно с моделью, и все элементы управления, использующие эту модель, отключатся автоматически. Это сделает ваш код удивительно элегантным и чистым, легким в чтении и поддержке — в этом сама суть MVC. Чуть дальше мы узнаем об еще одном средстве удобной поддержки разных элементов управления с одинаковым предназначением — интерфейсе Action.

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

A теперь рассмотрим, о каких событиях может сообщать кнопка JButton и как в своей программе получать извещения об этих событиях.

Обработка событий от кнопок

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

//ButtonEvents.java

//Обработка событий от кнопок JButton import javax.swing.*;

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

import java.awt.event.*;

public class ButtonEvents extends JFrame { private JTextArea info;

public ButtonEvents() { super("ButtonEvents"); setDefaultCloseOperation( EXIT_ON_CLOSE );

//создаем кнопку и помещаем ее на север окна

JButton button = new JButton("Нажмите меня!"); add(button, "North");

//поле для вывода сообщений о событиях

info = new JTextArea("Пока событий не было\n");

246

ГЛАВА 9

add(new JScrollPane(info));

//привязываем к нашей кнопке слушателей событий

//слушатели описаны как внутренние классы button.addActionListener(new ActionL()); button.addChangeListener(new ChangeL());

//присоединение слушателя прямо на месте button.addItemListener(new ItemListener() {

public void itemStateChanged(ItemEvent e) { info.append("Это вы все равно не увидите");

}

});

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

setSize(400, 300); setVisible(true);

}

class ActionL implements ActionListener { public void actionPerformed(ActionEvent e) {

info.append(

"Получено сообщение о нажатии кнопки! От — " + e.getActionCommand() + "\n");

}

}

class ChangeL implements ChangeListener { public void stateChanged(ChangeEvent e) {

info.append(

"Получено сообщение о смене состояния кнопки!\n"); // это источник события

Object src = e.getSource();

}

}

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

new Runnable() {

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

}

}

Пример сам по себе очень прост — создается окно, на север которого помещается кнопка JButton, а в центр — многострочное текстовое поле JTextArea, вложенное в панель прокрутки JScrollPane. В это поле мы будем помещать сообщения о событиях, принятых кнопкой от пользователя.

Элементы управления (в том числе кнопки), унаследованные от класса AbstractButton, могут посылать сообщения о трех типах событий (за исключением стандартных событий, общих для всех компонентов Swing). Эти события перечислены в табл. 9.3.

Элементы управления

247

Таблица 9.3. События элементов управления, унаследованных от класса AbstractButton

Событие

Описание

 

 

ActionEvent (слушатель

Самое понятное событие для любой кнопки и, пожалуй, самое

ActionListener)

простое и наиболее часто используемое событие вообще в лю-

 

бом графическом Java-приложении. Посылается кнопкой, когда

 

пользователь щелкает на ней (нажимает и отпускает), чтобы про-

 

извести действие

ChangeEvent (слушатель

Это событие мало что означает собственно для кнопок; с его по-

ChangeListener)

мощью модель кнопок ButtonModel взаимодействует со своим

 

UI-представителем. Модель при изменении хранящегося в ней

 

состояния кнопки (это может быть изменение включенного состо-

 

яния на выключенное, обычного на нажатое и т. п.) запускает со-

 

бытие ChangeEvent. UI-представитель обрабатывает это событие

 

и соответствующим образом перерисовывает кнопку. Впрочем, вы

 

можете обрабатывать это событие и сами, в том случае если вас

 

интересуют малейшие изменения в состоянии кнопки

ItemEvent (слушатель Item-

Это событие (мы рассмотрим его чуть позже) посылают компонен-

Listener)

ты, которые имеют несколько равноценных состояний (например,

 

флажки и переключатели), чтобы сообщить о смене состояния

 

 

После создания кнопки к ней присоединяются слушатели событий, по одному на каждое возможное событие. При этом также демонстрируются возможные способы присоединения слушателей. Для событий ActionEvent и ChangeEvent слушатели помещаются в отдельные внутренние классы, а слушатель события ItemEvent создается прямо на месте, как анонимный внутренний класс. Слушателей событий у кнопок может быть произвольное количество, поскольку кнопки относятся к компонентам с групповой (multicast) обработкой событий3.

Запустив программу, вы увидите, как появляются события, читая сообщения в текстовом поле.

3 На самом деле все компоненты в Swing поддерживают произвольное количество слушателей, благодаря использованию простого и эффективного средства для хранения слушателей — класса EventListenerList. Мы подробно обсуждали его в главе 2, когда создавали свои собственные типы событий.

248

ГЛАВА 9

Заметьте, что при щелчке на кнопке слушатель события определяет имя нажатой кнопки, используя метод getActionCommand() класса ActionEvent. Этот метод применяется для того, чтобы при обработке событий от нескольких кнопок в одном слушателе можно было их отличить. В качестве имени можно использовать произвольную строку символов, устанавливая ее методом setActionCommand() класса AbstractButton. Иногда этот механизм позволяет элегантно решать некоторые задачи. Например, если таким образом передавать имя класса, объект которого необходимо создать, то один слушатель будет способен обработать неограниченное количество кнопок.

События ActionEvent и ChangeEvent вообще несут не слишком много полезной информации, так как предполагается, что в приложении используется наиболее элегантный подход — каждому компоненту сопоставляется свой слушатель событий. Единственное, что можно себе позволить — это получить ссылку на источник события методом getSource(). Однако будьте осторожнее с вызовом этого метода, потому что это может привести к сильной связи между интерфейсом и деловой логикой, а это всегда нежелательно.

Запустив программу, вы также увидите, что событие ItemEvent не возникает вовсе. Это совсем не удивительно, потому что оно «работает» только с флажками, переключателями и другими компонентами, имеющими состояние, а кнопки его просто игнорируют.

Это все о событиях от кнопок JButton. У других элементов управления имеются дополнительные события, но в большинстве своем вам будет достаточно тех событий, которые мы только что обсудили. Говорить больше не о чем — остается лишь оценить простоту и мощь, которую способен привнести в программы продуманный объектноориентированный подход.

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

Мнемоники

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

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

//ButtonMnemonics.java

//Поддержка кнопками клавиатурных мнемоник import javax.swing.*;

import java.awt.*;

public class ButtonMnemonics extends JFrame {

Элементы управления

249

public ButtonMnemonics() { super("ButtonMnemonics"); setDefaultCloseOperation( EXIT_ON_CLOSE );

//используем последовательное расположение setLayout(new FlowLayout());

//создаем кнопку

JButton button = new JButton("Нажмите меня!");

//мнемоника (русский символ) button.setMnemonic('Н'); add(button);

//еще одна кнопка, только надпись на английском button = new JButton("All Right!"); button.setMnemonic('L'); button.setToolTipText("Жмите смело"); button.setDisplayedMnemonicIndex(2); add(button);

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

pack();

setVisible(true);

}

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

new Runnable() {

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

}

}

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

setMnemonic(). Позволяет указать мнемонику, то есть то, клавиша какого символа в сочетании с управляющей клавишей (Alt) будет вызывать нажатие кнопки. Можно просто указать символ (в одинарных кавычках, регистр не учитывается), а можно использовать константы из класса java.awt.event.KeyEvent, но первый подход проще и понятнее.

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

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

4 Пользуясь этим методом, не забывайте, что отсчет символов строки ведется с нуля.

250

ГЛАВА 9

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

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

в Swing. Если внимательно изучить исходный текст базового UI-представителя всех элементов управления Swing BasicButtonUI, то выясняется, что поддержка мнемо-

ник обеспечивается двумя событиями в карте входных событий (данные карты мы подробно обсуждали в главе 5) — это события с названиями «pressed» и «released». Данным событиям сопоставлены соответствующие действия из карты команд: первое действие переводит кнопку в нажатое состояние (когда пользователь нажимает и удерживает мнемонику), второе «отпускает» кнопку (когда пользователь отпускает

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

ствующие нажатиям мнемоник, своими, используя в качестве событий клавиатурные

сокращения с кириллическими символами. Но есть одно препятствие: еще в главе 5 мы отметили, что система событий Java и класс событий от клавиатуры KeyEvent не

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

и «отпускание», а просто станут еще одним клавиатурным сокращением. Ситуация может быть исправлена только переписыванием события KeyEvent, а на это вряд ли

стоит рассчитывать в ближайшее время.

Ну а пока, чтобы не обрекать приложение на беспомощность в случае отсутствия мыши, можно использовать такую «заплатку»:

JButton button = new JButton("Файл (F)"); button.setMnemonic('F');

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

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

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

Элементы управления

251

Интерфейс Action

Хороший стиль программирования пользовательских интерфейсов предусматривает четкое разделение интерфейса и деловой части программы. При таком подходе получаются более надежные программы, которые проще поддерживать и расширять. Как вы уже могли убедиться, Swing полностью поддерживает такое разделение с помощью своей модели событий. Однако при этом возникают и проблемы. При создании графического Java-приложения слушатели (обработчики) событий чаще всего располагаются во внутренних классах. Присоединить их не составляет труда — вы просто создаете экземпляр класса и передаете его компоненту. Однако ситуация в корне меняется, когда в приложении есть несколько компонентов, к которым необходимо присоединить одного и того же слушателя (чаще всего такими компонентами являются элементы управления, отвечающие за одно и то же действие, например кнопка на панели инструментов и команда меню). Решение кажется очевидным — создать для каждого компонента отдельный экземпляр слушателя событий, но это расточительно и более того, затрудняет управление программой (например, если действие недоступно, придется вручную выключать все связанные с этим действием компоненты).

Для решения этих проблем в библиотеку Swing специально для элементов управления был включен новый интерфейс Action, расширяющий интерфейс ActionListener и позволяющий сосредоточить всю информацию о команде в одном месте. Теперь, создав всего один экземпляр класса, отвечающего за команду5, вы сможете передать его всем нужным элементам управления, которые сами настроят свой внешний вид. Более того, в интерфейс Action встроена поддержка извещений об изменениях в команде — если вы что-то измените, все элементы управления автоматически узнают об этом. Рассмотрим небольшой пример:

//ActionSample.java

//Использование архитектуры Action import javax.swing.*;

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

public class ActionSample extends JFrame { public ActionSample() {

super("ActionSample"); setDefaultCloseOperation( EXIT_ON_CLOSE );

//используем последовательное расположение setLayout(new FlowLayout());

//создадим пару кнопок, выполняющих

//одно действие

Action action = new SimpleAction(); JButton button1 = new JButton(action); JButton button2 = new JButton(action); add(button1);

add(button2);

5Мы не случайно перешли от термина «слушатель» к термину «команда» — если вы знакомы

сшаблонами проектирования, то поймете, что интерфейс Action представляет собой именно команду.

252 ГЛАВА 9

// выводим окно на экран setSize(300, 100); setVisible(true);

}

// этот внутренний класс инкапсулирует нашу команду class SimpleAction extends AbstractAction {

SimpleAction() {

// установим параметры команды putValue(NAME, "Привет, Action!"); putValue(SHORT_DESCRIPTION, "Это подсказка"); putValue(MNEMONIC_KEY, new Integer('A'));

}

//в этом методе обрабатывается событие, как

//и в прежнем методе ActionListener

public void actionPerformed(ActionEvent e) {

//можно выключить команду, не зная, к

//каким компонентам она присоединена setEnabled(false);

//изменим надпись

putValue(NAME, "Прощай, Action!");

}

}

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

new Runnable() {

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

}

}

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

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

Гораздо интереснее, как устроен класс SimpleAction. Сам он не реализует довольно громоздкий интерфейс Action (так бы пришлось все писать «с нуля»), а задействует уже готовую заготовку, расширяя абстрактный класс AbstractAction. В данном абстрактном классе уже имеется поддержка слушателей PropertyChangeListener, которые оповещаются об изменениях в параме-

Элементы управления

253

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

Параметры команды хранятся в виде пар ключ-значение, где ключ — одна из строк, определенных в интерфейсе Action. Эта строка показывает, какой именно параметр команды хранится в паре. Для того чтобы изменить параметр, используется метод putValue(). Параметры, которые мы изменили в нашем примере, перечислены в табл. 9.4.

Таблица 9.4. Параметры для интерфейса Action

Параметр

Описание

NAME

Этому ключу отвечает название команды, то есть надпись, которая

 

будет выведена на кнопке или в меню

SHORT_DESCRIPTION

Ключ отвечает за краткое описание команды, появляющееся в виде

 

всплывающей подсказки

MNEMONIC_KEY

Данный ключ позволяет указать мнемонику команды (заметьте, что

 

для этого приходится создавать отдельный объект Integer для хра-

 

нения кода клавиши)

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

Запустив программу, вы увидите две полностью настроенные и готовые к работе кнопки. Мы для этого не вызвали ни одного метода класса JButton, а просто указали в конструкторе, какое действие должна выполнять кнопка.

Элементы управления с двумя состояниями

Кроме кнопок, рассмотренных нами в предыдущем разделе, в приложениях очень часто приходится использовать другие элементы управления, для которых характерно наличие двух устойчивых состояний. К ним относятся флажки (check boxes), переключатели (radio buttons) и выключатели (toggle buttons). Поддержка их также встроена в класс AbstractButton, и единственное отличие их от кнопок JButton состоит в том, что они могут находиться в одном из двух состояний и при смене состояний генерируют событие ItemEvent (которое кнопки игнорируют). Все остальные свойства, рассмотренные нами выше для кнопок JButton, верны и для них.

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

JToggleButton.

Выключатели JToggleButton

Выключатель JToggleButton — довольно необычный элемент управления, и встретить его в простом пользовательском интерфейсе не так-то просто. Гораздо чаще он гостит на панелях инструментов, где с успехом заменяет флажки, которые из-за своего «непрезентабельного» вида иначе бы «портили» стройные ряды графических кнопок. Фактически, по виду это та же самая кнопка, только ее можно нажать, и она останется нажатой, а не «выпрыгнет» обратно. Можно использовать этот элемент управления и в обычном интерфейсе, например, когда нужно выбрать что-то из двух альтернатив, а применять

254

ГЛАВА 9

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

Рассмотрим на примере, как работает выключатель, а потом обсудим остальные детали его поведения:

//ToggleButtons.java

//Использование выключателей JToggleButton import javax.swing.*;

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

public class ToggleButtons extends JFrame { public ToggleButtons() {

super("ToggleButtons"); setDefaultCloseOperation( EXIT_ON_CLOSE );

//используем последовательное расположение setLayout(new FlowLayout());

//создадим пару кнопок JToggleButton button1 = new JToggleButton("Первая", true); button2 = new JToggleButton("Вторая", false);

//добавим слушатель события о смене состояния button2.addItemListener(new ItemListener() {

public void itemStateChanged(ItemEvent e) { button1.setSelected(

! button2.isSelected());

}

});

add(button1);

add(button2);

// выводим окно на экран pack(); setVisible(true);

}

// ссылки на используемые кнопки private JToggleButton button1, button2; public static void main(String[] args) {

SwingUtilities.invokeLater( new Runnable() {

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

}

}

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