Стандартная поставка Java Platform, Standard Edition (Java SE), включает РІ себя богатейшую библиотеку классов, обеспечивающих создание графического интерфейса пользователя GUI (Graphical User Interface). Рта графическая библиотека получила название JFC (Java Foundation Classes). Р’ библиотеке JFC можно выделить шесть основных частей:
□ AWT (Abstract Window Toolkit) — базовая библиотека классов с несколько устарелым набором "тяжелых" (heavyweight) графических компонентов, расположенная в пакете j ava. awt и его подпакетах. Мы уже рассмотрели ее возможности в предыдущих главах;
□ Swing — библиотека "легких" (lightweight) графических компонентов, дополняющая и во многом заменяющая библиотеку AWT. Занимает почти двадцать пакетов с префиксом j avax. swing;
□ Java 2D — часть библиотеки AWT, обеспечивающая рисование графики, выбор цвета, вывод изображений и фигурного текста, а также преобразование их перед выводом. Ее возможности уже показаны в главе 9;
□ DnD (Drag and Drop) — библиотека классов, позволяющих перемещать объекты из одного компонента в другой с помощью буфера обмена (clipboard). Классы этой библиотеки помещены в пакеты j ava. awt. datatrans fer и j ava. awt. dnd;
□ Input Method Framework — классы для создания новых методов ввода/вывода. Они занимают пакеты j ava. awt .im и j ava. awt. im.spi;
□ Accessibility — библиотека классов для взаимодействия с нестандартными устройствами ввода/вывода: клавиатурой Брайля, световым пером и др. Она расположена в пакете javax.accessibility.
Основное средство построения графического интерфейса пользователя в технологии Java — это библиотека Swing. Она может применяться везде, где установлен пакет Java Runtime Environment (JRE). Для браузеров, в которые не встроен пакет JRE, корпорация Oracle выпускает модуль Java Plug-in, автоматически загружающийся с сайта при загрузке апплета, использующего классы библиотеки Swing. Модуль Java Plug-in входит в состав JRE и автоматически подгружается в браузер, работающий там, где установлен пакет JRE. Нужно лишь, чтобы браузер распознавал тег
В состав Java SE в число демонстрационных программ входят апплет и приложение SwingSet2, расположенные в каталоге $JAVA_HOME/demo/jfc/SwingSet2/. Они показывают большинство возможностей Swing. Там же можно посмотреть исходные тексты соответствующих классов. Если в состав браузера входит библиотека Swing, то для просмотра апплета достаточно загрузить в браузер файл SwingSet2.html. Если Swing в браузере нет, то для загрузки Java Plug-in надо загрузить в браузер файл SwingSet2Plugin.html.
Состав библиотеки Swing
Библиотека Swing очень велика. Она содержит более пятисот классов и интерфейсов, предоставляющих богатейшие возможности оформления графического интерфейса. Полное описание ее возможностей занимает более тысячи страниц. Но большинство классов, входящих в Swing, предназначено для удовлетворения самых изысканных потребностей разработчика, окончательной "доводки" графического интерфейса, придания ему особого лоска.
Для построения же стандартного интерфейса достаточно возможностей, предоставляемых классами из пакета javax.swing. Очень легко создать стандартное окно приложения. В листинге 11.1 показан шаблон для приложения, использующего библиотеку Swing.
Листинг 11.1. Шаблон приложения, использующего Swing
import j ava.awt.*; // Базовые классы AWT.
import j avax.swing.*; // Основные классы Swing.
public class SwingApplicationTemplate extends JFrame{
public SwingApplicationTemplate(String title){
// Создаем основное окно. super(title);
// Получаем контейнер верхнего уровня.
// Для JDK 5.0 и выше это необязательно.
Container c = getContentPane();
// Помещаем компонент в контейнер. c.add(xxxx);
// Прочие установки...
// Задаем начальную ширину и высоту окна. setSize(500, 400);
// Завершаем работу приложения при закрытии окна.
setDefaultCloseOperation(EXIT ON CLOSE);
// Выводим окно на экран. setVisible(true);
public static void main(String[] args){
new SwingApplicationTemplate("Заголовок основного окна");
}
}
Если создаваемое приложение должно предоставить пользователю нестандартное диалоговое окно выбора цвета (а стандартное окно — это экземпляр класса JColorChooser), то понадобится пакет javax.swing.colorchooser.
При создании диалогового окна выбора файла (окно Открыть или Сохранить как) методами класса JFileChooser, для отбора файлов по типу или другому признаку применяются классы из пакета javax. swing. filechooser.
Для обработки содержимого таблицы — экземпляра класса JTable — пригодится пакет
j avax.swing.table.
Работу с объектами, расположенными в виде дерева типа JTree, можно организовать с помощью классов пакета javax.swing.tree.
При создании текстового редактора большую помощь окажут классы из пакета javax.swing.text. Они помогут создать нужную форму курсора, отследить и изменить его позицию, выделить фрагмент текста, задать формат записи дат и чисел и многое другое.
Возможность отмены и повтора действий (undo/redo) в текстовом редакторе обеспечивают классы пакета j avax. swing. undo.
Подпакеты javax.swing.text.html и javax.swing.text.rtf дадут возможность текстовому редактору работать с форматами HTML и RTF, а классы подпакета j avax. swing. text. html. parser содержат средства синтаксического разбора HTML-файлов.
Для оформления рамок различного вида, ограничивающих группы компонентов, предназначены классы из пакета javax.swing.border.
Наконец, пять пакетов javax.swing.plaf.* задают внешний РІРёРґ Рё поведение приложения (Look and Feel, L&F) РІ различных графических средах. Можно сделать РІРёРґ Рё поведение независимым РѕС‚ графической оболочки операционной системы. РўРѕРіРґР° приложение РІ любой графической оболочке будет выглядеть одинаково Рё РІ равной степени реагировать РЅР° действия мыши Рё клавиатуры. Можно, наоборот, сделать так, что РІ каждой графической среде: MS Windows, CDE/Motif, Macintosh, приложение будет выглядеть как "СЂРѕРґРЅРѕРµ" для этой среды Рё реагировать РЅР° внешние воздействия РїРѕ правилам данной графической среды. Можно заложить изменение внешнего РІРёРґР° Рё поведения РІ настройки приложения, сделав его изменяемым (Pluggable Look and Feel, PL&F, PLAF или plaf) РїРѕ желанию пользователя. Рту возможность можно реализовать классами пакета
j avax.swing.plaf.multi.
Технология Java предлагает СЃРІРѕР№ собственный стиль, называемый "Java Look and Feel", ранее называвшийся стилем "Metal". Ртот стиль Сѓ нас РёРЅРѕРіРґР° называется "приборным" стилем, потому что приложение, оформленное РІ этом стиле, выглядит как алюминиевая панель научного РїСЂРёР±РѕСЂР°. "Р РѕРґРЅРѕР№" стиль Java L&F реализуется классами пакета j avax. swing.plaf.metal Рё принимается РїРѕ умолчанию РІ технологии Java. РќР° сайте есть подробнейшее руководство РїРѕ созданию графического интерфейса пользователя РІ стиле Java L&F — "Java Look and Feel Design
Guidelines". Разумеется, РІ этой РєРЅРёРіРµ РјС‹ РЅРµ сможем полностью рассмотреть РІСЃРµ возможности библиотеки Swing, РЅРѕ ее структуру Рё основные средства РѕСЃРІРѕРёРј РІ той мере, которая позволит создать СѓРґРѕР±РЅРѕРµ Рё красивое приложение, приятное для работы. Рзложение библиотеки Swing РІ этой части РєРЅРёРіРё рассчитано РЅР° то, что читатель знаком СЃ постоянно обновляемым электронным учебником "The Java Tutorial. A practical guide for programmers", расположенным РїРѕ адресу tutorial/.
Начнем с обзора готовых графических компонентов Swing.
Основные компоненты Swing
В библиотеку Swing входит около тридцати готовых графических компонентов: надписи, кнопки, поля ввода, линейки прокрутки, ползунки, меню и пункты меню, деревья, таблицы. Они собраны главным образом в пакет javax.swing. Рассмотрим их последовательно от самых простых до самых сложных компонентов. Но начнем с вершины иерархии компонентов класса JComponent.
Компонент JComponent
Основные свойства всех компонентов Swing сосредоточены РІ РёС… суперклассе JComponent. Класс JComponent расширяет класс Container, входящий РІ графическую библиотеку AWT. Поэтому компонент JComponent Рё РІСЃРµ его расширения являются контейнерами Рё РјРѕРіСѓС‚ содержать РІ себе РґСЂСѓРіРёРµ компоненты. Класс Container, РІ СЃРІРѕСЋ очередь, расширяет класс Component, содержащий около сотни методов работы СЃ компонентами. Рти методы Рё методы класса Container наследуются классом JComponent, который добавляет Рє РЅРёРј РґРѕР±СЂСѓСЋ сотню СЃРІРѕРёС… методов. Р’СЃРµ компоненты Swing расширяют класс JComponent, наследуя его богатейшие свойства.
Класс JComponent - это абстрактный класс, поэтому нельзя создать его экземпляры. Кроме того, он реализован как "легкий" компонент и, несмотря на то что является контейнером, не может служить контейнером верхнего уровня. По этим причинам он не используется самостоятельно, а только как суперкласс для создания новых компонентов, перенося на них всю свою мощь.
В классе JComponent сосредоточена основная функциональность компонентов Swing. Перечислим некоторые возможности компонентов.
□ Для компонента JComponent и его наследников можно задать рамку методом
setBorder(Border).
□ Компонент можно сделать прозрачным или непрозрачным с помощью метода
setOpaque(boolean).
□ Фон непрозрачного компонента можно закрасить определенным цветом методом
setBackground(Color) .
□ Для любого компонента можно установить шрифт методом setFont(Font) и его цвет методом setForeground(Color).
□ Для каждого компонента создается графический контекст класса Graphics, которым можно воспользоваться для рисования фигур и линий на компоненте, обратившись к методу paint (Graphics ).
□ Можно задать определенную форму курсора мыши методом setCursor(Cursor). Такую форму курсор мыши будет принимать, когда он проходит над компонентом.
□ Каждый компонент разрешается снабдить всплывающей подсказкой, которая появится, если задержать на секунду курсор мыши над компонентом. Для этого достаточно задать текст всплывающей подсказки методом setToolTipText(String).
□ Для каждого компонента можно определить минимальный, максимальный и предпочтительный размер методами setMinimumSize(Dimension), setMaximumSize(Dimension) и setPreferredSize(Dimension) соответственно, а также собственную локаль - методом
setLocale(Locale).
□ Все компоненты отслеживают события клавиатуры KeyEvent и мыши MouseEvent, MouseWheelEvent, передачу фокуса FocusEvent, события изменения компонента
ComponentEvent и контейнера ContainerEvent.
У компонентов Swing сложное строение — они построены по схеме MVC.
Схема MVC в компонентах Swing
Конструктивная схема Модель-Вид-Контроллер (MVC, Model-View-Controller) рассмотрена нами в главе 3. Повторим вкратце ее основные понятия.
Первую часть, Model, составляет РѕРґРёРЅ или несколько классов, РІ которых хранится или вырабатывается РІСЃСЏ информация, обрабатываемая компонентом, Рё текущее состояние объектов, созданных этим компонентом. Рти классы обладают методами setXXX() РІРІРѕРґР° Рё изменения информации.
Вторая часть - РѕРґРёРЅ или несколько классов, составляющих View. Рта часть компонента описывает СЃРїРѕСЃРѕР± представления результатов, сгенерированных Моделью, РЅР° экране дисплея, принтере или РґСЂСѓРіРѕРј устройстве РІ определенном РІРёРґРµ: таблица, график, диаграмма. Рљ РѕРґРЅРѕР№ Модели можно подключить несколько Р’РёРґРѕРІ, РїРѕ-разному представляющих РѕРґРЅРё Рё те же результаты или отражающие разную информацию. Р’РёРґС‹ получают информацию методами getXxx() Рё isXxx () Модели.
Третья часть — классы, образующие Controller, — создают интерфейс для ввода информации и изменения состояния объекта. Они реагируют на события ввода с клавиатуры, действия мыши и прочие воздействия на объект и обращаются к методам setXxx () Модели, изменяя ее поля или вызывая генерацию информации. Одна Модель может использоваться несколькими Контроллерами.
Р’РёРґ Рё Контроллер РЅРµ взаимодействуют. Контроллер, реагируя РЅР° события, обращается Рє методам setXxx() Модели, которые меняют хранящуюся РІ ней информацию. Модель, изменив информацию, сообщает РѕР± этом тем Видам, которые зарегистрировались Сѓ нее. Ртот СЃРїРѕСЃРѕР± взаимодействия Модели Рё Р’РёРґР° получил название "РїРѕРґРїРёСЃРєР°-рассылка" (subscribe-publish). Р’РёРґС‹ подписываются Сѓ Модели, Рё та рассылает РёРј сообщения Рѕ РІСЃСЏРєРѕРј изменении состояния объекта методами fireXxx(), после чего Р’РёРґС‹ забирают измененную информацию, обращаясь Рє методам getXxx() Рё isXxx() Модели.
Р’ библиотеке Swing модели описаны интерфейсами, РІ которых перечислены необходимые методы. РЈ каждого интерфейса есть хотя Р±С‹ РѕРґРЅР° стандартная реализация, принимаемая компонентами Swing РїРѕ умолчанию. Некоторые классы реализуют сразу несколько интерфейсов, некоторые интерфейсы реализованы несколькими классами. Рти интерфейсы Рё классы, реализующие РёС…, приведены РІ табл. 11.1.
Таблица 11.1. Рнтерфейсы моделей Рё классы, реализующие РёС… | |
Рнтерфейс | Класс |
BoundedRangeModel | DefaultBoundedRangeModel |
ButtonModel | De faultButtonModel |
JToggleButton.ToggleButtonModel | |
ComboBoxModel | De faultComboBoxModel |
MutableComboBoxModel | |
ListModel | AbstractListModel |
DefaultListModel | |
ListSelectionModel | DefaultListSelectionModel |
SingleSelectionModel | DefaultSingleSelectionModel |
ColorSelectionModel | DefaultColorSelectionModel |
SpinnerModel | AbstractSpinnerModelSpinnerDateModelSpinnerListModelSpinnerNumberModel |
TableColumnModel | DefaultTableColumnModel |
TableModel | DefaultTableModel |
TreeModel | DefaultTreeModel |
TreeSelectionModel | DefaultTreeSelectionModel |
JTree.EmptySelectionModel |
В графическом интерфейсе пользователя очень часто Вид и Контроллер работают с одними и теми же графическими компонентами. Контроллер связан с нажатием кнопок, протаскиванием мыши по линейкам прокрутки и движкам, вводом текста в поля ввода, а Вид меняет на экране эти графические компоненты, получив от Модели сообщение о происшедших изменениях.
Для реализации модели MVC библиотека Swing использует делегирование (delegation) полномочий, назначая в качестве модели данных представителя (delegate) — экземпляр класса с именем вида xxxModel. Класс, описывающий компонент, содержит защищенное или даже закрытое поле model — объект этого класса-модели, и метод getModel (), предоставляющий разработчику доступ к полю model. Сложные компоненты могут иметь несколько моделей, например в классе JTable есть три поля-представителя:
protected TableColumnModel columnModel;
protected TableModel dataModel;
protected ListSelectionModel selectionModel;
и, соответственно, три метода доступа:
TableColumnModel getColumnModel();
TableModel getModel();
ListSelectionModel getSelectionModel();
Делегирование полномочий используется и для обеспечения PL&F. Класс JComponent содержит защищенное поле ui — экземпляр класса-представителя ComponentUI из пакета javax.swing.plaf, непосредственно отвечающего за вывод изображения на экран в нужном виде. Класс-представитель содержит методы paint() и update(), формирующие и обновляющие графические примитивы. Такие представители образуют целую иерархию с общим суперклассом ComponentUI. Они собраны в пакет javax.swing.plaf и его подпакеты. В их именах есть буквы UI (User Interface), например: ButtonUI, BasicButtonUI.
Представители класса тоже являются полями класса компонента, а доступ к ним осуществляется методами вида getUI ().
Класс, описывающий компонент, дублирует большинство методов модели, например в том же классе JTable есть множество методов доступа к информации getXxx(), большинство из них просто обращаются к соответствующим методам модели, например метод получения числа строк таблицы:
public int getRowCount(){
return getModel().getRowCount();
}
Поэтому при построении графического интерфейса пользователя редко приходится обращаться к моделям и представителям компонента. В большинстве случаев достаточно обращаться к методам самого класса компонента. Если модель, принятая по умолчанию, в чем-то не устраивает разработчика, можно заменить ее другой моделью, реализовав подходящий интерфейс или расширив существующий класс xxxModel. Новая модель данных устанавливается методом setModel (xxxModel). Если приложение не обращалось непосредственно к методам модели, то в нем ничего изменять не придется.
Надпись JLabel
Различные неизменяемые надписи и небольшие изображения в окне приложения представляются компонентом JLabel. Для создания экземпляра этого класса есть шесть конструкторов.
□ Конструктор по умолчанию JLabel () выделяет прямоугольную область в контейнере без надписи и изображения, в которую потом можно поместить текст методом
setText (String) и изображение методом setIcon (Icon).
в–Ў Конструкторы JLabel (String) Рё JLabel(Icon) выделяют прямоугольную область Рё заносят РІ нее строку текста — экземпляр класса String, или изображение — экземпляр класса, реализующего интерфейс Icon, обычно это класс ImageIcon. Рзображение размещается РІ центре области, Р° строка РІ центре РїРѕ вертикали Рё слева.
в–Ў Р’ конструкторах СЃ РґРІСѓРјСЏ параметрами JLabel(String, int) Рё JLabel (Icon, int) второй параметр задает горизонтальное размещение текста или изображения константами LEFT, CENTER, RIGHT, LEADING или TRAILING интерфейса SwingConstants. Ртот интерфейс реализован РІ классе JLabel. Понятия leading Рё trailing зависят РѕС‚ установленной ло-кали. Для языков СЃ написанием слева направо это левая Рё правая сторона области, для РґСЂСѓРіРёС…, например арабского языка, наоборот.
Размещение можно потом изменить методом setHorizontalAlignment(int). Можно изменить и размещение по вертикали методом setVerticalAlignment(int) с константами
TOP, CENTER или BOTTOM.
в–Ў Последний конструктор, JLabel(String, Icon, int), задает Рё строку, Рё изображение, Рё размещение, РїСЂРё этом строка располагается справа РѕС‚ изображения для языков СЃ написанием слева направо. Рзменить расположение текста относительно изображения РїРѕ горизонтали Рё вертикали можно методами setHorizontalTextPosition(int) Рё setVerticalTextPosition (int) СЃ такими же константами. РџРѕ умолчанию текст РѕС‚ изображения отделяют 4 пиксела. Рзменить это расстояние можно методом
setIconTextGap(int).
Например, создание надписи и размещение ее сверху и справа в выделенной для компонента области контейнера выглядит так:
JLabel l = new JLabel("Какая-то надпись", JLabel.RIGHT);
l.setVerticalAlignment(JLabel.TOP);
Если же мы хотим разместить в компоненте JLabel текст и изображение, причем текст расположить слева от изображения, оставив между ними 10 пикселов, то надо сделать примерно так:
JLabel l = new JLabel("Надпись",
new ImageIcon("myimage.gif"), JLabel.CENTER);
l.setHorizontalTextPosition(JLabel.LEFT); l.setIconTextGap(10);
Рнтересно, что Swing "понимает" разметку языка HTML Рё РІ строке можно СЃ помощью тегов менять цвет, шрифт, создавать СЃРїРёСЃРєРё, размещать текст РІ нескольких строках:
l.setText("Первая строка вторая");
Внимание!
Тег должен начинать строку, идти сразу же после открывающей кавычки, без пробелов.
При этом следует учитывать, что размер компонента может быть вычислен неправильно и проинтерпретированный текст не поместится в компоненте. Поэтому, например, вместо тега .
Еще одно интересное свойство. Компонент JLabel можно связать с другим компонентом методом setLabelFor(Component). Затем с какой-либо буквой надписи, например А, нужно связать командную клавишу методом
setDisplayedMnemonic(’A’);
или методом
setDisplayedMnemonic(KeyEvent.VK A);
Второй из этих методов требует включения в программу пакета j ava. awt. event. Буква a в надписи будет подчеркнута. После этого нажатие комбинации клавиш
Если в надписи несколько одинаковых букв, то будет подчеркнута первая из них. Методом setDisplayedMnemonicIndex (int) можно подчеркнуть букву с указанным в качестве параметра индексом. У первой буквы надписи нулевой индекс.
При переводе компонента JLabel в недоступное состояние методом setEnabled(false) текст и изображение становятся бледными. Можно при этом заменить изображение, если оно уже было в компоненте, другим изображением с помощью метода setDisabledlcon(Icon).
Остальные методы класса JLabel выполняют проверки и предоставляют сведения о компоненте, но не забывайте, что можно воспользоваться еще и методами классов JComponent, Container и Component. Достаточно просто установить цвет надписи методом setForeground(Color), шрифт- методом setFont(Font), цвет фона- методом
setBackground(Color). При этом учтите, что по умолчанию компонент JLabel прозрачен, и перед закрашиванием фона надо сделать его непрозрачным, обратившись к методу setOpaque (true). Можно обрамить компонент методом setBorder(Border). Можно задать всплывающую подсказку методом setToolTipText(String). Можно даже задать реакцию на внешние воздействия, но для этого лучше применять кнопки.
РљРЅРѕРїРєРё
Библиотека Swing предлагает целую иерархию кнопок, показанную на рис. 11.1. В нее включены и пункты меню JMenultem, и кнопки выбора JCheckBox, и радиокнопки
JRadioButton.
JComponent
AbstractButton -i-JButton
Р•
BasicArrowButton
MetalComboBoxButton
-JMenultem—JCheckBoxMenultem —JMenu
— JRadioButtonMenultem
ElToggleButton-i—JCheckBox LjRadioButton
Р РёСЃ. 11.1. Рерархия классов РєРЅРѕРїРѕРє
Во главе иерархии стоит абстрактный класс AbstractButton, содержащий методы, общие для всех типов кнопок. В нем также собраны константы, определяющие общее поведение всех кнопок.
Все кнопки типа AbstractButton реагируют на событие ActionEvent, происходящее при щелчке кнопкой мыши, событие класса ChangeEvent из пакета j avax. swing.event, возникающее при всех действиях мыши: наведении курсора мыши на компонент, удалении его с компонента, нажатии кнопки мыши и т. д., и на событие itemEvent, возникающее при смене состояния кнопки.
На любую кнопку всегда можно поместить новый текст методом setText(String) и сменить существующее изображение методом setIcon(Icon).
Как и в компоненте класса JLabel, можно методом setDisabledIcon(Icon) сменить изображение на кнопке, сделанной недоступной, т. е. на кнопке, к которой применен метод setEnabled (false). При попытке выделения недоступной кнопки можно установить на ней новое изображение методом setDisabledSelectedIcon(Icon).
Кроме того, можно сменить изображение при наведении курсора мыши на кнопку методом setRolloverIcon(Icon), но только если предварительно эта возможность включена методом setRolloverEnabled (true). По умолчанию она отключена.
Аналогично можно сменить изображение при выделении кнопки методом setSelectedIcon(Icon) , при наведении курсора мыши на выделенную кнопку методом
setRolloverSelectedIcon (Icon), РїСЂРё нажатии РєРЅРѕРїРєРё мыши setPressedIcon(Icon). Рти возможности всегда включены.
Ртак, СЃ РѕРґРЅРѕР№ РєРЅРѕРїРєРѕР№ допустимо связать семь изображений Рё заменять РёС… РїСЂРё наведении РєСѓСЂСЃРѕСЂР° мыши РЅР° РєРЅРѕРїРєСѓ, нажатии РєРЅРѕРїРєРё мыши, выделении РєРЅРѕРїРєРё, наведении РєСѓСЂСЃРѕСЂР° мыши РЅР° выделенную РєРЅРѕРїРєСѓ, РїСЂРё переводе РєРЅРѕРїРєРё РІ недоступное состояние Рё РїСЂРё выделении недоступной РєРЅРѕРїРєРё. Следует заметить, что РЅРµ РІСЃРµ графические системы реализуют перечисленные возможности.
Командную клавишу можно назначить кнопке методом setMnemonic(int) с указанием в качестве параметра этого метода константы из класса java.awt.event.KeyEvent. Будет подчеркнута первая буква надписи, связанная с командной клавишей. Как и в классе JLabel, можно подчеркнуть не только первое появление этой буквы, но и какое-нибудь из следующих появлений с помощью метода
setDisplayedMnemonicIndex(int) .
Всплывающая подсказка для кнопки задается методом setToolTipText(String).
РљРЅРѕРїРєРё типа AbstractButton используют РїРѕ умолчанию модель класса DefaultButtonModel, реализующего интерфейс ButtonModel. Рта модель отслеживает пять состояний РєРЅРѕРїРєРё.
□ Кнопка находится в состоянии "наведенная" (rollover), когда над ней располагается курсор мыши. Контроллер отмечает это состояние методом setRollover(boolean) модели, а вид курсора определяется методом isRollover ( ).
в–Ў Р’ состояние "наготове" (armed) РєРЅРѕРїРєР° переходит РїСЂРё нажатии РЅР° ней РєРЅРѕРїРєРё мыши. Рто состояние устанавливается РІ модели методом setArmed(boolean), Р° отслеживается логическим методом isArmed ( ).
□ В состояние "нажатая" (pressed) кнопка переходит из состояния "наготове" после отпускания кнопки мыши. За этим состоянием следят методы setPressed(boolean) и
isPressed().
□ После щелчка кнопка "выделяется" (selected), что отмечается методами
setSelected(boolean) Рё isSelected().
□ Наконец, кнопку можно сделать "доступной" (enabled) или "недоступной" (disabled) методом setEnabled(boolean) и отследить ее состояние методом isEnabled ( ).
Класс AbstractButton дублирует только методы setEnabled (boolean) и isEnabled(), остальные состояния надо отслеживать методами модели DefaultButtonModel, получив предварительно ее экземпляр методом getModel ( ).
На практике, разумеется, применяются не объекты класса AbstractButton, а расширения этого класса, которые мы рассмотрим подробнее. Самое небольшое расширение, реализующее все свойства кнопки AbstractButton, это компонент JButton.
РљРЅРѕРїРєР° JButton
Обычная прямоугольная РєРЅРѕРїРєР° — экземпляр класса JButton — может, так же как Рё Jlabel, содержать текст Рё/или изображение. РС… размещение Рё взаимное положение РЅРµ задается конструктором, Р° устанавливается методами
setHorizontalAlignment(int); setVerticalAlignment(int); setHorisontalTextPosition(int); setVerticalTextPosition(int); setIconTextGap(int);
точно так же, как и в компонентах Jlabel, и с теми же константами в качестве параметра этих методов. По умолчанию и текст, и изображение располагаются по центру кнопки, а изображение слева от текста.
Для создания объектов класса JButton есть пять конструкторов: конструктор по умолчанию JButton (), конструкторы кнопок с текстом JButton (String) и изображением Jbutton (Icon), конструктор с двумя параметрами JButton(String, Icon). Пятый конструктор, JButton (Action), использует объект класса, реализующего интерфейс Action.
Как прямое расширение класса AbstractButton, класс JButton наследует все его свойства и методы и к нему относится все сказанное в предыдущем разделе. С учетом этого определение кнопки может выглядеть так:
ImageIcon def = new ImageIcon("default.gif");
JButton b = new JButton(,,Далее,,, def); b.setBackground(new Color(183, 220, 65)); b.setFont(new Font("Lucida", Font.ITALIC, 12)); b.setPreferredSize(new Dimension(100, 30)); b.setMnemonic(KeyEvent.VK L);
b.setToolTipText("Переход к следующей странице"); b.setRolloverEnabled(true);
b.setRolloverIcon(new ImageIcon("rollover.gif")); b.setSelectedIcon(new ImageIcon("select.gif")); b.setRolloverSelectedIcon(new ImageIcon("rollselect.gif")); b.setPressedIcon(new ImageIcon("press.gif")); b.setDisabledIcon(new ImageIcon("disable.gif")); b.setDisabledSelectedIcon(new ImageIcon("disselect.gif")); b.setActionCommand("next"); b.addActionListener(this) ; b.addChangeListener(this); b.addItemListener(this);
Кнопка JButton реагирует на событие ActionEvent, возникающее при щелчке кнопкой мыши на компоненте, событие ChangeEvent, происходящее при всех действиях мышью на компоненте, и событие ItemEvent. Кроме того, кнопка наследует события ComponentEvent и ContainerEvent, а также события мыши и клавиатуры.
Кнопка выбора JToggleButton
Компонент JToggleButton представляет прямоугольную РєРЅРѕРїРєСѓ стандартного РІРёРґР°, имеющую РґРІР° состояния, отмечаемые как булево значение true/false, Рё меняющую РѕРґРЅРѕ состояние РЅР° РґСЂСѓРіРѕРµ РїСЂРё щелчке РєРЅРѕРїРєРѕР№ мыши РЅР° компоненте или нажатии "горячей" клавиши. Рзменение L&F РїСЂРё смене состояния обычно заключается РІ том, что РєРЅРѕРїРєР° РЅР° экране становится "нажатой" Рё остается РІ этом состоянии РґРѕ следующего щелчка РєРЅРѕРїРєРѕР№ мыши. Отследить текущее состояние РєРЅРѕРїРєРё можно логическим методом isSelected (), установить то или РґСЂСѓРіРѕРµ состояние программно — методом
setSelected(boolean) .
Ркземпляры класса JToggleButton создаются восемью конструкторами. РћСЃРЅРѕРІРЅРѕР№ конструктор
JToggleButton(String, Icon, boolean);
создает кнопку с надписью, изображением и выбранным значением true или false. В других конструкторах отсутствуют какие-то из этих параметров, причем отсутствующий третий параметр считается равным false. В строке можно сделать разметку HTML. Конструктор JToggleButton(Action) использует в качестве параметра экземпляр класса, реализующего интерфейс Action.
В листинге 11.2 показан простейший пример кнопки с двумя состояниями.
Листинг 11.2. Простейшая кнопка с двумя состояниями
import java.awt.*; import java.awt.event.*; import javax.swing.*;
class DummyToggleButton extends JFrame{
private JToggleButton tb;
public DummyToggleButton(){
tb = new JToggleButton("Да? Нет?"); tb.setMnemonic(KeyEvent.VK L); tb.setToolTipText("Сделайте выбор"); add(tb);
// Для JDK версии ранее 5.0 уберите комментарий // getContentPane().add(tb);
setSize(300,300);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE); setVisible(true);
}
public static void main(String[] args){ new DummyToggleButton();
}
}
Класс JToggleButton применяется обычно как суперкласс для создания кнопок выбора нестандартного вида. Для получения стандартных кнопок выбора используются его подклассы JCheckBox и JRadioButton.
Кнопка выбора JCheckBox
Компонент JCheckBox - это стандартная кнопка выбора с меткой или изображением слева или справа от надписи, в которой показывается состояние кнопки: true или false. Форма метки зависит от установки L&F.
Создать экземпляр класса JCheckBox можно одним из восьми конструкторов. Основной конструктор- JCheckBox (String, Icon, boolean), в других конструкторах те же парамет
ры, что и у класса JToggleButton.
Класс JCheckBox не добавляет функциональности своему суперклассу и используется точно так же, как класс JToggleButton.
Радиокнопка JRadioButton
Стандартная радиокнопка создается одним из восьми конструкторов, основной из
них- JRadioButton (String, Icon, boolean), остальные имеют те же параметры, что и
конструкторы класса JToggleButton.
Класс JRadioButton не добавляет функциональности своему суперклассу и используется точно так же, как класс JToggleButton.
Радиокнопки имеет смысл использовать только в составе группы, обеспечивая выбор одного из нескольких значений. Для получения группы сначала создается пустая группа как экземпляр класса ButtonGroup, затем она заполняется радиокнопками методом
add(AbstractButton).
Группа радиокнопок никак не выделяется на экране, это только логическое объединение элементов. Чтобы выделить кнопки, входящие в группу, их обычно размещают на отдельной панели, окружая эту панель рамкой. В листинге 11.3 показано обычное размещение группы радиокнопок. Кнопки в листинге устанавливают цвет фона окна приложения. На рис. 11.2 показан вид этой группы радиокнопок.
Листинг 11.3. Группа радиокнопок
import java.awt.*; import javax.swing.*; import javax.swing.border.*;
class RadioButtonTest extends JFrame{
public RadioButtonTest(){ setBackground(Color.white); setLayout(new FlowLayout());
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.X AXIS)); p.setBorder(BorderFactory.createEtchedBorder());
JRadioButton rb1 =
new JRadioButton ( "Rрасный фон,,); rb1. setMnemoni c (KeyEvent. VK R);
rb1.setToolTipText("E^i выбираете красный фон");
rb1.addActionListener(this); rb1.setActionCommand("red");
JRadioButton rb2 =
new JRadioButton (XhtmlXuX^u>еленый фон,,);
rb2. setMnemoni c (KeyEvent .VK P);
rb2.setToolTipText("Вы выбираете^Хеленый фон"); rb2.addActionListener(this); rb2.setActionCommand("green");
JRadioButton rb3 =
new JRadioButton ( XhtmlXuXX/u>иний фон" ); rb3.setMnemoni c(KeyEvent.VK C);
rb3.setToolTipText("Вы выбираете синий фон"); rb3.addActionListener(this); rb3.setActionCommand("blue");
ButtonGroup bg = new ButtonGroup(); bg.add(rb1); bg.add(rb2); bg.add(rb3);
p.add(rb1); p.add(rb2); p.add(rb3); add(p);
setSize(300, 150);
setDefaultCloseOperation(JFrame.EXIT ON CLOSE); setVisible(true);
}
public static void main(String[] args){ new RadioButtonTest() ;
}
}
Группу радиокнопок составляют несколько объектов класса JRadioButton, что при большом числе вариантов приводит к неоправданному расходу ресурсов. Кроме того, такая группа занимает много места в окне приложения. Для выбора из большого числа вариантов библиотека Swing предлагает создать объекты одного из двух классов: JList и JComboBox.
Упражнение
1. Перепишите листинг 10.1 с использованием компонентов Swing.
Раскрывающийся список JComboBox
Выбор одного варианта из большого числа возможностей удобно организовать с помощью класса JComboBox. Выбранный элемент виден в окне компонента, остальные элементы списка раскрываются при щелчке кнопкой мыши по стрелке, находящейся справа в поле компонента. Раскрывшееся окно — это экземпляр класса JPopupMenu.
Создать экземпляр раскрывающегося списка можно конструктором по умолчанию JComboBox ( ), а затем заносить в него элементы методами addItem(Object) и insertItemAt(Object, int).
Однако чаще бывает удобнее занести элементы в список сразу же при его создании конструктором JComboBox (Obj ect [ ] ) или JComboBox(Vector), предварительно создав массив или вектор, содержащий элементы. Например:
String[] data = {"Рванов", "Петров", "РЎРёРґРѕСЂРѕРІ"};
JComboBox cb = new JComboBox(data);
Если в списке будет использована модель, отличная от модели, принятой по умолчанию, то элементы сначала заносятся в нее, а затем конструктором JComboBox (ComboBoxModel) создается объект, связанный с этой моделью.
В список можно занести и изображения, например:
Object[] data = {new ImageIcon("apple.gif"), new ImageIcon("grape.gif"), new ImageIcon("pear.gif")};
JComboBox fruits = new JComboBox(data);
Есть возможность заносить в список объекты и других типов. Мы поговорим о реализации этой возможности в разд. "Визуализация элементов списков” данной главы.
После создания СЃРїРёСЃРєР° РІ его РѕРєРЅРµ виден первый элемент. Чтобы поместить РІ РѕРєРЅРѕ какой-то РґСЂСѓРіРѕР№ элемент, следует обратиться Рє методам setSelectedItem(Object) или setSelectedIndex(int). Если РІ этих методах указать РІ качестве параметра null или -1 соответственно, то РѕРєРЅРѕ будет пустым. Рто СѓРґРѕР±РЅРѕ РІ случае редактируемого СЃРїРёСЃРєР°, РІ РѕРєРЅРѕ которого можно вводить новый элемент или редактировать выбранный. РЎРїРёСЃРѕРє становится редактируемым после выполнения метода setEditable (true). Для редактирования привлекается текстовый редактор — экземпляр класса, реализующего интерфейс ComboBoxEditor. РџРѕ умолчанию используется РѕРґРЅР° РёР· реализаций этого интерфейса — класс BasicComboBoxEditor, открывающий для редактирования поле РІРІРѕРґР° класса JTextField.
Редактирование выбранного элемента списка в окне не приводит к изменению этого элемента в списке, а влияет только на объект, возвращаемый методом getSelectedItem( ).
Текст HTML интерпретируется в элементах списка, но в окне редактируемого списка при использовании в качестве редактора объекта класса BasicComboBoxEditor появляется в "плоском" ASCII-виде и в таком же виде возвращается методом getSelectedItem( ).
По умолчанию список раскрывается полностью. Чтобы ограничить раскрывающееся окно несколькими строками, нужно обратиться к методу
setMaximumRowCount(int);
Если в списке больше элементов, чем выделено строк этим методом, то в раскрывающемся окне появится полоса прокрутки.
При выборе элемента или окончании редактирования (нажатии клавиши
getSelectedItem (), а его индекс — методом getSelectedIndex ().
Моделью данных для класса JComboBox служит класс DefaultComboBoxModel, реализующий сразу три интерфейса: ListModel, ComboBoxModel и MutableComboBoxModel, и расширяющий класс AbstractListModel. Нет никакой необходимости в непосредственном обращении к методам этой модели, поскольку они дублируются методами класса JComboBox, за одним исключением. В модели данных класса DefaultComboBoxModel при изменении списка происходит событие класса ListDataEvent, не отслеживаемое классом JComboBox. Но эта модель не реагирует на события ActionEvent и ItemEvent.
Больше того, сам класс JComboBox зачем-то реализует интерфейсы ActionListener, EventListener, ListDataListener, но использовать его как слушателя событий нельзя.
Есть еще несколько нестыковок. Р’ классе JComboBox элементы называются Item, например такое РёРјСЏ использовано РІ названии метода getItemAt(int). Р’ классе DefaultComboBoxModel аналогичный метод называется getElementAt (int). Рто приходится учитывать РїСЂРё создании собственной модели данных.
Наконец, попытка задать в одном списке JComboBox элементы с текстом и изображением приведет к их смешению. Причину этого рассмотрим в разд. "Визуализация элементов списков" данной главы.
Список выбора JList
Вместо РіСЂСѓРїРїС‹ РєРЅРѕРїРѕРє выбора можно создать СЃРїРёСЃРѕРє класса JList. Р’ таком СЃРїРёСЃРєРµ допустимо выбирать РЅРµ только РѕРґРёРЅ элемент, РЅРѕ Рё РіСЂСѓРїРїСѓ РїРѕРґСЂСЏРґ идущих элементов, Рё несколько таких РіСЂСѓРїРї. РљСЂРѕРјРµ конструктора РїРѕ умолчанию JList (), создающего пустой СЃРїРёСЃРѕРє, можно задать СЃРїРёСЃРѕРє СЃ заданным массивом объектов конструктором JList (Obj ect [ ]), СЃ заданным вектором РїСЂРё помощи конструктора JList(Vector) или СЃ определенной заранее моделью JList(ListModel). Рто делается так же, как Рё РїСЂРё создании экземпляра класса JComboBox.
Список типа JList выглядит на экране просто как столбец из всех своих элементов. Чтобы ограничить число видимых на экране строк и снабдить список полосой прокрутки для показа остальных строк, следует поместить список на панель типа JScrollPane. После этого можно задать число видимых строк методом setVisibleRowCount(int). C учетом всего этого определение списка выбора может выглядеть так:
JFrame f = new JFrame();
String[] data = {"Рванов", "Петров", "РЎРёРґРѕСЂРѕРІ"};
JList list = new JList(data);
list.setVisibleRowCount(2);
list.addListSelectionListener(this);
JScrollPane sp = new JScrollPane(list); f.getContent Pane().add(sp);
Так же, как и в раскрывающийся список JComboBox, в список JList можно занести не только текст, но и изображения.
По умолчанию в списке можно выбрать любое число любых элементов, держа клавишу
setSelectionMode(ListSelectionModel.SINGLE SELECTION);
РІ СЃРїРёСЃРєРµ можно будет выбрать только РѕРґРёРЅ элемент. Третья возможность — отметить РѕРґРёРЅ диапазон РїРѕРґСЂСЏРґ идущих элементов — достигается использованием РІ качестве параметра этого метода константы single_interval_selection. Рти три возможности выбора элементов СЃРїРёСЃРєР° — результат реализации интерфейса
ListSelectionModel классом DefaultListSelectionModel. Данная реализация модели выбора применяется в классе JList по умолчанию. Если такая реализация почему-либо не устраивает разработчика, то он может реализовать интерфейс ListSelectionModel своим классом и установить созданную модель выбора методом
setSelectionModel(ListSelectionModel).
РћРґРёРЅ (первый) выбранный элемент можно получить методом getSelectedValue (), массив типа Object [] всех выбранных элементов — методом getSelectedValues (). Рндекс первого выбранного элемента выдает метод getSelectedindex(), массив индексов всех выбранных элементов — метод getSelectedIndices ( ).
Кроме модели выбора- реализации интерфейса ListSelectionModel- класс JList свя
зан еще с моделью данных. Она описана интерфейсом ListModel и частично реализована абстрактным классом AbstractListModel. Класс JList использует расширение этого класса — класс DefaultListModel. Класс DefaultListModel хранит данные в закрытом поле delegate типа Vector на основе идеи делегирования и дублирует фактически все методы класса Vector, обращаясь к классу-представителю, например:
public Object getElementAt(int index){ return delegate.elementAt(index);
}
Класс JList отслеживает событие ListSelectionEvent, происходящее при смене выделенного элемента списка. Его модель данных отслеживает, кроме того, событие ListDataEvent, возникающее при всяком изменении списка.
Визуализация элементов списков
Компоненты классов JList и JComboBox могут содержать десятки и сотни элементов, имеющих тип String или Icon. Создание графического объекта для каждого элемента списка приведет к колоссальному расходу оперативной памяти и к большим затратам времени на создание объектов. Чтобы избежать этого расхода ресурсов, для изображения элементов списков назначается объект-рисовальщик. Он последовательно выводит элементы на экран или на принтер, переходя от одного элемента к другому. Короче говоря, реализуется design pattern, известный под именем Flyweight.
Кроме экономии ресурсов такой подход дает возможность вывода каждого элемента списка по-своему, меняя вид элемента, например шрифт или цвет. Класс-рисовальщик описан в интерфейсе ListCellRenderer, имеющем только один метод
public Component getListCellRendererComponent(
JList list, // Список, элементы которого выводятся на экран
// Рлемент СЃРїРёСЃРєР°, который будет выведен // Порядковый индекс этого элемента // Выбран ли этот элемент?
Object value, int index, boolean isSelected, boolean cellHasFocus
// Рмеет ли фокус этот элемент?
Ртот метод должен сформировать компонент Рё поместить РІ него текущий элемент СЃРїРёСЃРєР° value, имеющий порядковый номер index. Р’РёРґ компонента может зависеть РЅРµ только РѕС‚ его класса или РїРѕСЂСЏРґРєРѕРІРѕРіРѕ номера, РЅРѕ Рё РѕС‚ того, выбран ли РѕРЅ isSelected (обычно выбранный элемент выделяется СЃРёРЅРёРј цветом фона) Рё имеет ли фокус cellHasFocus (обычно обводится тонкой рамкой). Полученный компонент затем выводится РЅР° экран СЃРІРѕРёРј методом paint ( ).
Р’ библиотеке Swing интерфейс ListCellRenderer реализован классами BasicComboBoxRenderer Рё DefaultListCellRenderer, расширяющими класс JLabel. Рменно потому, что выводом элементов фактически занимается класс JLabel, можно использовать РІ элементах СЃРїРёСЃРєР° текст или изображение. Рнтересный эффект получится, если смешать РІ РѕРґРЅРѕРј СЃРїРёСЃРєРµ типа JComboBox Рё текст, Рё изображения. Класс BasicComboBoxRenderer попытается вывести РёС… вместе. РЎРїРёСЃРѕРє типа JList, РІ котором текст перемежается изображениями, будет выведен правильно. Р’СЃРµ дело РІ разной реализации интерфейса ListCellRenderer. Р’РѕС‚ фрагмент реализации:
public class DefaultListCellRenderer extends JLabel implements ListCellRenderer, Serializable{
public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){
setComponentOrientation(list.getComponentOrientation());
if (isSelected){
setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground());
}else{
setBackground(list.getBackground()); setForeground(list.getForeground());
}
if (value instanceof Icon){ seticon((Icon)value);
setText("");
}else{
seticon(null);
setText((value == null) ? "" : value.toString());
}
setEnabled(list.isEnabled()); setFont(list.getFont()); setBorder((cellHasFocus) ?
UIManager.getBorder("List.focusCellHighlightBorder”) : noFocusBorder);
return this;
}
}
Как видно из этого фрагмента, в каждый формирующийся компонент — один элемент списка JList — может быть помещено либо изображение типа Icon, либо текст типа String. Всякий другой объект будет преобразован в строку его методом toString (), в том числе и объект класса JLabel. Легко изменить эту реализацию, убрав условие if(value instanceof Icon) из приведенного фрагмента и применив унаследованные от класса JLabel методы
setText(((JLabel)value).getText()); seticon(((JLabel)value).geticon());
После этого элементами списка могут служить объекты класса JLabel. Но это еще не все. Метод getSelectedValue () по-прежнему будет возвращать строку, выдаваемую методом JLabel. toString (), а не ссылку на объект. Значит, надо еще расширить класс JLabel, переписав метод toString ( ).
Ртак, если разработчику нужно создать СЃРїРёСЃРѕРє, содержащий объекты РґСЂСѓРіРёС… типов, отличных РѕС‚ String Рё Icon, то РѕРЅ должен написать класс, экземпляры которого Р±СѓРґСѓС‚ служить элементами СЃРїРёСЃРєР°. Р’ данном классе следует переопределить, РєСЂРѕРјРµ методов getXxx()/setXxx(), метод toString(), Р° РїСЂРё необходимости Рё метод paint (). Ркземпляры этого класса записываются РІ конструктор JList(Object[]) Рё передаются методу getListCellRendererComponent () как параметр value.
Потом следует написать свою реализацию интерфейса ListCellRenderer. Обычно она расширяет класс JLabel или JPanel.
Упражнение
2. Перепишите листинг 10.2 с использованием компонентов Swing.
Счетчик JSpinner
Перебирать элементы списка часто бывает удобнее с помощью счетчика — небольшого редактируемого поля с текущим значением списка и двумя стрелками вверх и вниз, с помощью которых можно заменять текущее значение предыдущим или следующим. Самый простой такой счетчик создается конструктором по умолчанию JSpinner ( ). Его
текущее значение — 0, следующее — 1, предыдущее--1, и так можно перебирать все
целые числа.
Для создания более сложного счетчика конструктором JSpinner(SpinnerModel) придется сначала определять модель данных. Она описана интерфейсом SpinnerModel, частично реализована абстрактным классом AbstractSpinnerModel и полностью реализована в трех классах. Класс spinnerDateModel реализует модель, содержащую даты, класс SpinnerListModel — модель, содержащую коллекцию типа List, в частности, массив произвольных объектов типа object[]. Класс spinnerNumberModel содержит целые или вещественные числа или объекты класса Number.
Например, следующая строка:
JSpinner sp = new JSpinner(new SpinnerNumberModel(50, 0, 100, 5));
создает счетчик с текущим значением 50, диапазоном значений от 0 до 100 и шагом изменения значений 5. В поле можно ввести любое значение из указанного диапазона, например 47, тогда предыдущее значение будет равно 42, а следующее — 52.
С помощью класса SpinnerNumberModel можно создать еще модель с вещественными числами конструктором
SpinnerNumberModel(double current, double min, double max, double step);
и числовую модель общего вида конструктором
SpinnerNumberModel(Number current, Comparable min, Comparable max, Number step);
Значения min и max могут быть null, в таком случае нижняя или верхняя границы не существуют.
Текущее, предыдущее Рё следующее значение можно получить РѕС‚ счетчика JSpinner методами getValue (), getPreviousValue() Рё getNextValue() соответственно. Рти методы возвращают объект класса Object. Если значения выходят Р·Р° заданные РІ конструкторе границы, то указанные методы возвращают null.
При всяком изменении текущего значения, а также окончании ввода в поле нового значения путем нажатия клавиши
sp.addChangeListener(this);
// . . . .
public void stateChanged(ChangeEvent e){ comp.setValue((int)sp.getValue());
}
Вторая модель данных класса, SpinnerDateModel, позволяет выбирать даты из заданного списка. Конструктор по умолчанию SpinnerDateModel () создает счетчик с текущей датой и временем, предыдущее его значение — это то же время вчерашнего дня, следующее значение — то же время завтрашнего дня, ограничений на выбор нет.
Конструктор
SpinnerDateModel(Date value, Comparable first,
Comparable last, Date step);
задает произвольную текущую дату value, диапазон значений дат от first до last и шаг step. Если одно или оба значения диапазона равны null, то соответствующая граница отсутствует. Шаг step определяет также и форму представления даты и может принимать значения одной из констант: era, year, month, week_of_year, week_of_month, DAY_OF_MONTH, DAY_OF_YEAR, DAY_OF_WEEK, DAY_OF_WEEK_IN_MONTH, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND класса Calendar.
Более широкие возможности предоставляет третья модель данных класса —
SpinnerListModel. В конструкторе SpinnerListModel(Object[] ) этого класса можно задать массив произвольных объектов, например:
String[] data = {"Дворник", "Уборщица", "Программист", "Сторож"};
SpinnerListModel model = new SpinnerListModel(data);
JSpinner emp = new JSpinner(model);
В другом конструкторе, SpinnerListModel(List), задается экземпляр коллекции, реализующей интерфейс List, например экземпляр класса Vector.
Хотя РІ счетчик можно заложить любые объекты, РІ поле будет показана только строка, полученная методом toString () текущего объекта. Рто РїСЂРѕРёСЃС…РѕРґРёС‚ потому, что редактор РїРѕ умолчанию, заложенный РІ класс JSpinner, — это экземпляр класса
JFormattedTextField. Определяет его вложенный в JSpinner класс DefaultEditor и его подклассы DateEditor, ListEditor и NumberEditor.
Полосы прокрутки JScrollBar
Полосы прокрутки используются многими компонентами и контейнерами Swing. В массе случаев достаточно поместить компонент на панель типа JScrollPane, как это сделано в листинге 11.3, чтобы обеспечить прокрутку содержимого компонента.
Полосы прокрутки определяются четырьмя числами-свойствами, хранящимися РІ модели данных. Рто наименьшее значение полосы minimum, наибольшее значение maximum, текущее значение value Рё шаг изменения extent. РћС‚ последнего числа зависит размер РІРёРґРёРјРѕР№ области компонента, связанного СЃ полосой прокрутки, Рё длина ползунка РЅР° полосе прокрутки. Действия СЃ этими числами описаны интерфейсом BoundedRangeModel. РџСЂРё РІСЃСЏРєРѕРј изменении данных чисел РїСЂРѕРёСЃС…РѕРґРёС‚ событие ChangeEvent. Класс JScrollBar использует РІ качестве модели данных реализацию DefaultBoundedRangeModel этого интерфейса.
Полосы прокрутки создаются конструктором
JScrollBar(int orientation, int value, int extent, int min, int max);
или конструктором JScrollBar(int orientation), устанавливающим значения min = 0, max = 100, value = 0, extent = 10. Вертикальная или горизонтальная ориентация задается константами vertical или horizontal.
Все значения можно потом изменить методами setMinimum(int), setMaximum (int), setValue(int) и setExtent(int).
Текущее значение возвращается методом getValue(). Метод getUnitIncrement(int) возвращает величину перемещения РЅР° РѕРґРЅСѓ единицу вверх, если параметр этого метода равен —1, или РІРЅРёР·, если параметр равен 1. Рта величина устанавливается равной 1 РїСЂРё создании полосы Рё может быть изменена методом setUnitincrement(int). Аналогично метод getBlockincrement (int) возвращает величину перемещения вверх или РІРЅРёР· РЅР° РѕРґРёРЅ блок. Размер блока вначале равен величине extent, затем ее можно изменить методом
setBlockIncrement(int).
Полоса прокрутки реагирует на событие AdjustmentEvent, происходящее при каждом изменении ее модели данных.
Ползунок JSlider
Ползунок представляет собой линейку с указателем, которым можно установить какое-то значение value из диапазона min-max. Внутренне ползунок устроен так же, как и полоса прокрутки.
Компонент JSlider тоже использует модель данных класса DefaultBoundedRangeModel с наименьшим, наибольшим и текущим значениями, а также шагом изменения. Впрочем, можно применить другую модель, задав ее в конструкторе JSlider(BoundedRangeModel) или установив методом setModel(BoundedRangeModel) .
Основной конструктор:
JSlider(int orientation, int min, int max, int value);
В других конструкторах отсутствует тот или иной параметр, при этом устанавливается горизонтальный ползунок со значениями min = 0, max = 100, value = (min + max)/2.
Рядом с линейкой ползунка можно разметить шкалу со штрихами, отстоящими друг от друга на расстояние, устанавливаемое методом setMajorTickSpacing(int). Вначале это расстояние равно нулю. После определения расстояния шкала задается методом setPaintTicks(true) . К штрихам можно добавить числовые значения методом setPaintLabels (true). Между штрихами допускается размещение более мелких штрихов методом setMinorTickSpacing ( int). Если применить метод setSnapToTicks (true), то движок ползунка будет останавливаться только против штрихов.
Основную линейку ползунка можно убрать методом setPaintTrack(false), оставив только шкалу.
Числовые значения в шкале ставятся против каждого штриха. Методом createStandardLabels ( int incr, int start) можно изменить это правило, задав другой шаг расстановки чисел incr на шкале и другой начальный отсчет start. Затем этот шаг надо установить на шкале методом setLabelTable(Dictionary). Все это удобно делать вместе, например после определений:
JSlider sl = new JSlider();
sl.setMaj orTickSpacing(10); sl.setMinorTickSpacing(5); sl.setPaintTicks(true); sl.setPaintLabels(true);
sl.setLabelTable(sl.createStandardLabels(20, 28));
получим отмеченные значения 28, 48, 68, 88, как показано на рис. 11.3.
Метод setLabelTable(Dictionary) позволяет сделать и более сложные изменения, установив в качестве меток не только числа, но и какие-то другие значения словаря типа
Di ctionary.
Внешний вид ползунка определяется абстрактным классом sliderUi. У него два расширения — классы BasicSliderUi и MultiSliderUi. На рис. 11.3 ползунок нарисован расширением класса BasicSliderUI — классом MetalSliderUI. При желании можно создать свой класс-рисовальщик, расширив один из этих классов и установив новый класс методом
setUI(SliderUi).
При перемещении движка в ползунке происходит событие ChangeEvent. В процессе обработки этого события можно получить значение ползунка методом getValue ( ).
Упражнение
3. Перепишите листинг 10.4, заменив полосы прокрутки ползунками Swing.
Рндикатор JProgressBar
Рндикатор, часто называемый "градусником", показывает степень выполнения какого-то процесса, чаще всего РІ процентах. Процесс должен вырабатывать какое-РЅРёР±СѓРґСЊ целое число. Р’ конструкторе индикатора
JProgressBar(int orientation, int min, int max);
задаются наименьшее min и наибольшее max значения этого числа. В других конструкторах опущены некоторые из указанных параметров. При этом ориентация считается горизонтальной, min = 0, max = 100.
РџРѕ мере выполнения процесса РѕРЅ должен передавать степень своего выполнения РІ индикатор методом setvalue (int). Рто значение немедленно отражается РІ индикаторе. После обращения Рє методу setStringPainted(true) РІ РѕРєРЅРµ индикатора появится еще число — процент выполнения процесса.
Если время выполнения процесса, связанного СЃ индикатором, РЅРµ определено, то можно перевести индикатор РІ неопределенный режим (indeterminate mode). Рто делается методом setindeterminate(true). Р’ этом режиме индикатор мигает, показывая, что процесс выполняется. РљРѕРіРґР° окончание процесса определится, надо занести наибольшее значение процесса РІ индикатор методом setMaximum(int), текущее значение методом setValue (int) Рё перевести индикатор РІ обычный режим методом setindeterminate ( false).
Внешний вид индикатора описывается абстрактным классом ProgressBarUI. У него два расширения — классы BasicProgressBarUI и MultiProgressBarUI. Стандартный вид Java L&F обеспечивается классом MetalProgressBarUi. При необходимости изменения внешнего вида индикатора следует расширить один из этих классов и установить новый вид методом setUI ( ProgressBarUI).
Рндикатор может работать РІ отдельном РѕРєРЅРµ, эту возможность предоставляет класс
ProgressMonitor.
Дерево объектов JTree
Дерево JTree располагает объекты РІ иерархическую структуру. РћРЅР° создается только РЅР° экране, РЅРѕ РЅРµ РІ оперативной памяти. РќР° СѓСЂРѕРІРЅРµ 0 находится РѕРґРёРЅ корневой (root) объект, РЅР° СѓСЂРѕРІРЅСЏС… 1, 2 Рё С‚. Рґ. размещаются его потомки (child) — узловые (node) объекты, имеющие СЃРІРѕРёС… потомков Рё РѕРґРЅРѕРіРѕ предка (parent). РќР° самом нижнем СѓСЂРѕРІРЅРµ расположены листья (leaf). Рто узлы, РЅРµ имеющие потомков.
Для СЌРєРѕРЅРѕРјРёРё ресурсов дерево РЅРµ определяется рекурсивно, его узлы РЅРµ являются ссылками типа JTree. Вместо этого узел описан интерфейсом TreeNode Рё его расширением — интерфейсом MutableTreeNode. Рто расширение добавляет методы замены объекта, находящегося РІ узле, Р° также методы добавления Рё удаления потомков РёР· узла. РћРЅРѕ реализовано классом DefaultMutableTreeNode РёР· пакета javax.swing.tree.
Узел дерева JTree создается конструктором DefaultMutableTreeNode(Object), в котором задается ссылка на содержащийся в узле объект.
Каждый узел класса DefaultMutableTreeNode содержит ссылку на своего предка, которую можно получить методом getParent (). Если этот метод возвращает null, значит, узел корневой, но для такой проверки есть специальный логический метод isRoot ().
Узел хранит ссылки на потомков в структуре типа Vector, получить их можно многочисленными методами getXxx (). Метод getLevel () показывает уровень узла относительно корня, а метод getDepth () — количество уровней поддерева, начинающегося с данного узла. Метод insert (MutableTreeNode, int) добавляет к узлу нового потомка в позицию, указанную вторым параметром. Метод setUserObject(Object) меняет ссылку на объект, расположенную в узле.
Узлы дерева можно сделать редактируемыми методом setEditable(true). Редактор должен реализовать методы интерфейса TreeCellEditor. По умолчанию используется реализация DefaultTreeCellEditor, открывающая для редактирования окно класса JTreeField.
Дерево класса JTree создается одним из семи конструкторов. Проще всего воспользоваться конструктором JTree(TreeNode), создающим корень дерева, а затем создавать и добавлять к дереву новые узлы.
Например:
DefaultMutableTreeNode root = new DefaultMutableTreeNode("KopeHb");
JTree tr = new JTree(root);
DefaultMutableTreeNode subtreel = new DefaultMutableTreeNode("Y3en 1"); root.add(subtree1);
subtree1.add(new DefaultMutableTreeNode('^HCT 2a"));
DefaultMutableTreeNode subtree2 = new DefaultMutableTreeNode("Y3en 2"); subtree1.add(subtree2);
subtree2.add(new DefaultMutableTreeNodeCVHHCT 3a")); subtree2.add(new DefaultMutableTreeNodeCVHHCT 3b")); subtree2.add(new DefaultMutableTreeNodeCVHHCT 3c"));
subtree1.add(new DefaultMutableTreeNode(YnucT 2b"));
root.add(new DefaultMutableTreeNode('^HCT 1"));
// Рё С‚. Рґ....
Для простоты каждый узел в этом дереве содержит строку класса String. Полученное дерево показано на рис. 11.4.
Против каждого узла выводится содержимое его объекта, преобразованное методом
toString().
Второй способ — сформировать вектор узлов дерева и воспользоваться конструктором JTree (Vector). Поддеревья создаются вложенными векторами. Вот как будет создано предыдущее дерево:
Vector root = new Vector();
Vector subtree1 = new Vector(); root.add(subtree1);
subtreel.add("Лист 2a");
Vector subtree2 = new Vector();
subtree1.add(subtree2); subtree2.add("Лист 3a"); subtree2.add("Лист 3b"); subtree2.add("Лист 3c"); subtree1.add('Vn^cT 2b");
root.add('^cT 1");
JTree tr = new JTree(root);
Недостаток этого метода в том, что у дерева, построенного с помощью вектора, нет корневого узла. Кроме того, в обоих вариантах узлы оказываются неподписанными, на экран выводится содержимое узлов в виде строки.
Второй недостаток можно устранить использованием конструктора JTree(Hashtable), аргумент которого — хеш-таблица. Вот как будет создано предыдущее дерево этим способом:
Hashtable root = new Hashtable();
Hashtable subtree1 = new Hashtable();
root.put("РЈР·eР» 1", subtreel);
Hashtable subtree2 = new Hashtable(); subtree1.put("Лист 2a", new Integer(21)); subtree1.put(,,Узeл 2", subtree2); subtree2.put("Лист 3a", new Integer(31)); subtree2.put("Лист 3b", new Integer(32)); subtree2.put("Лист 3c", new Integer(33)); subtree1.put("Лист 2b", new Integer(22));
root.put("Лист 1", new Integer(1));
JTree tr = new JTree(root);
РР· каждой пары "ключ — значение", хранящейся РІ хеш-таблице, ключ выводится РЅР° экран РІ РІРёРґРµ строки. РЈ этого дерева тоже нет РєРѕСЂРЅСЏ. РљСЂРѕРјРµ того, его узлы выводятся РЅРµ РІ том РїРѕСЂСЏРґРєРµ, РІ котором помещаются РІ хеш-таблицу, С‚. Рє. РѕРЅР° "перемешивает" СЃРІРѕРё данные.
Дерево с большим числом узлов удобно поместить на панель типа JScrollPane и указать число строк, помещающихся в окне, методом setVisibleRowCount(int), например:
JTree tr = new JTree(root);
JScrollPane sp = new JScrollPane(tr); tr.setVisibleRowCount(8);
В дереве можно выделить один или несколько узлов. Все узлы дерева пронумерованы сверху вниз, начиная от корневого узла, имеющего номер 0. Метод getSelectionRows ( ) возвращает массив типа int [ ] номеров выделенных узлов. Говоря точнее, нумерация узлов дерева описана интерфейсом RowMapper. В пакете javax.swing.tree есть абстрактный класс AbstractLayoutCache, реализующий этот интерфейс, и два его подкласса:
FixedHeightLayoutCache Рё VariableHeightLayoutCache.
РРЅРѕРіРґР° удобнее получить путь Рє узлу РІ РІРёРґРµ последовательности объектов, хранящихся РІ узлах, ведущих РѕС‚ РєРѕСЂРЅСЏ Рє данному узлу. Такая последовательность хранится РІ классе TreePath. Метод getSelectionPath() возвращает экземпляр этого класса, метод getSelectionPaths () возвращает массив таких экземпляров для всех выделенных узлов. Есть Рё РґСЂСѓРіРёРµ аналогичные методы получения экземпляров класса TreePath для различных узлов дерева.
Получив экземпляр класса TreePath, можно выбрать из него массив типа Object[] объектов, содержащихся в узлах, методом getPath(). Первый элемент этого массива всегда содержит объект, хранящийся в корне. Другие методы класса TreePath позволяют получить отдельные элементы этого массива.
Р’СЃСЏ информация Рѕ выделенных элементах дерева находится РІ модели выбора, описанной интерфейсом TreeSelectionModel Рё реализованной классом DefaultTreeSelectionModel. Данный класс хранит массив типа TreePath [ ] путей Рє выделенным узлам Рё методы работы СЃ этим массивом. РћРЅ предлагает три готовые модели выбора: выбор только РѕРґРЅРѕРіРѕ узла дерева single_tree_selection, выбор диапазона узлов contiguous_tree_selection Рё выбор нескольких диапазонов discontiguous_tree_selection. Последний выбор принимается РїРѕ умолчанию. Рзменить модель выбора РІ дереве tr можно так:
tr.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
Модель выбора реагирует на событие класса TreeSelectionEvent, происходящее при всяком новом выборе узлов. В отличие от большинства классов событий, класс TreeSelectionEvent содержит несколько полезных при обработке события методов, например метод getPaths ( ) выдает массив типа TreePath [ ].
Р’СЃСЏ информация РѕР± узлах дерева хранится РІ модели данных, описанной РІ интерфейсе TreeModel. Ртот интерфейс РЅРµ описывает класс узлов дерева. Узел может быть экземпляром любого класса. РќРѕ реализация DefaultTreeModel, принятая РІ классе JTree РїРѕ умолчанию, уже требует, чтобы узел имел интерфейс TreeNode.
Модель данных реагирует РЅР° событие класса TreeModelEvent, происходящее РїСЂРё изменении узлов дерева. Ртот класс события позволяет получить полезные данные Рѕ дереве, РІ частности, метод getTreePath() возвращает РІ РІРёРґРµ объекта класса TreePath путь Рє предку тех узлов, которые были изменены, добавлены или удалены.
РџРѕРґРѕР±РЅРѕ классу JList дерево типа JTree делегирует визуализацию классу-представителю, реализующему интерфейс TreeCellRenderer. Рнтерфейс описывает РѕРґРёРЅ метод:
Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus);
Как видно из этого описания, изображение узла value дерева tree может меняться в зависимости от его порядкового номера в дереве row и от того, выбран ли этот узел selected, раскрыта ли соответствующая ветвь дерева expanded, является ли узел листом leaf и имеет ли узел фокус hasFocus.
Данный интерфейс реализован классом DefaultTreeCellRenderer, добавляющим массу свойств и методов и расширяющим класс JLabel. Последнее свойство позволяет вывести на экран различные значки для узлов и листьев, открытых и закрытых ветвей дерева.
В состав Java SE JDK входит пример построения дерева шрифтов, расположенный в каталоге $JAVA_HOME/demo/jfc/SampleTree/. В нем показано, как можно изменить стандартную визуализацию дерева.
Построение меню средствами Swing
Р’СЃРµ компоненты, составляющие меню РІ библиотеке Swing: строка меню, пункты меню, всплывающее меню, имеют РѕРґРёРЅ Рё тот же тип, описанный интерфейсом MenuElement. Рто позволяет объединять элементы какого-либо подменю или РІСЃРµ меню, находящиеся РІ линейке меню, РІ массив СЃ помощью метода getSubElements() Рё рассматривать РёС… как РѕРґРЅРѕ целое.
Для создания системы меню сначала следует создать строку меню Рё установить ее РІ контейнер типа JFrame, JApplet, JDialog методом setJMenuBar(JMenuBar). Ртот метод располагает строку меню горизонтально ниже строки заголовка РѕРєРЅР°.
Строка меню JMenuBar
Строка меню создается единственным конструктором класса JMenuBar(). Полученная строка не содержит ни одного меню, их надо добавлять методом add(JMenu) по мере создания. Добавляемые меню будут располагаться слева направо в порядке обращения к методам add (JMenu). В некоторых графических системах меню Справка (Help) располагается справа. Чтобы учесть эту особенность, в класс JMenuBar включен специальный метод setHelpMenu (JMenu). Впрочем, этот метод реализован далеко не во всех выпусках JDK.
Начнем создавать примерное меню:
JFrame f = new JFrame("npnMep системы меню");
JMenuBar mb = new JMenuBar()); f.setJMenuBar(mb);
JMenu file = new JMenu("0aRn"));
JMenu edit = new JMenu("npaBKa"));
JMenu view = new JMenu("BHJ"));
JMenu help = new JMenu("CnpaBKa"));
mb.add(file); mb.add(edit); mb.add(view); mb.add(help);
Меню JMenu
Каждое меню по существу представляет собой два компонента: "кнопку" с текстом и всплывающее меню типа JPopupMenu, появляющееся при щелчке кнопкой мыши по этой "кнопке". Как видно из рис. 11.1, меню JMenu относится к типу кнопок, расширяющих класс AbstractButton. Кроме того, класс JMenu непосредственно расширяет класс пункта меню JMenultem. Следовательно, объект класса JMenu может служить пунктом какого-то другого меню, образуя таким образом подменю.
Меню создается конструктором JMenu(String). Второй конструктор JMenu (String, boolean) создает плавающее (tear-off) меню, если второй параметр равен true. Рто возможно РЅРµ РІРѕ всех графических системах.
Р’РЅРѕРІСЊ созданное меню РЅРµ содержит РЅРё РѕРґРЅРѕРіРѕ пункта. Пункты меню добавляются РѕРґРёРЅ Р·Р° РґСЂСѓРіРёРј методом add(JMenuitem) или методом add(string). Рнтересно, что эти методы возвращают ссылку РЅР° объект класса JMenultem, Р° второй метод сам создает такой объект. Еще РѕРґРёРЅ метод, add(Component), добавляет Рє меню произвольный компонент. Рто означает, что пунктом меню может служить любой компонент, РЅРѕ для встраивания РІ систему меню РѕРЅ должен реализовать интерфейс MenuElement. Например, РёРЅРѕРіРґР° пунктом меню служит раскрывающийся СЃРїРёСЃРѕРє JComboBox. РќРѕ чаще среди пунктов меню встречаются экземпляры подклассов класса JMenultem: подменю — объекты класса JMenu, РєРЅРѕРїРєРё выбора класса JCheckBoxMenuitem Рё радиокнопки класса
JRadioButtonMenultem.
В меню можно отделить одну группу пунктов от другой горизонтальной чертой с помощью метода addSeparator( ).
Пункты меню, включая разделительную черту, нумеруются сверху вниз, начиная от нуля. Методы insert(JMenuItem, int), insert(String, int) и add(Component, int) позволяют вставить новый пункт в указанную вторым параметром позицию, а метод insertSeparator (int) вставляет горизонтальную разделительную черту в указанную позицию.
Методы remove (Component), remove (int), remove (JMenultem) и removeAll ( ) удаляют пункты из меню. В сочетании с методами add() и insert () они позволяют динамически перестроить меню при изменении содержимого окна.
Меню, как и всякой кнопке, можно назначить командную клавишу методом setMnemonic (int). Добавим командные клавиши-акселераторы к меню нашего примера:
file.setMnemonic(KeyEvent.VK A); edit.setMnemonic(KeyEvent.VK G); view.setMnemonic(KeyEvent.VK D); help.setMnemonic(KeyEvent.VK C);
Меню реагирует на событие класса MenuEvent, происходящее при раскрытии, выборе пунктов и закрытии меню.
Пункт меню JMenuItem
Класс JMenultem расширяет класс AbstractButton и поэтому во многом наследует поведение кнопки. При создании пункта меню в нем можно задать текст
JMenultem (String), изображение JMenultem(Icon) или сразу и то и другое конструктором JMenultem(String, Icon). Взаимное положение текста и изображения можно отрегулировать так же, как это делалось для кнопки.
Добавим пункты и разделительную черту к меню Файл нашего примера. Забегая вперед, добавим и методы обработки событий. Объяснение этих методов будет дано в главе 15.
JMenu nw = new JMenu("Создать");
file.add(nw); // Добавляем как подменю
nw.addC'OaRn"); // Пункты подменю
nw.add("Сообщение"); nw.add("Образ");
JMenultem open = file.add("OTKpbiTb...");
JMenultem close = file.add("3aKpbiTb"); file.addSeparator();
// Другой способ:
JMenultem exit = new JMenuItem("Выход"); file.add(exit);
// Обрабатываем события:
open.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){
JFileChooser fch = new JFileChooser(); fch.showOpenDialog(null);
}
});
exit.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){
System.exit(0);
}
});
При создании пункта меню, содержащего текст, можно сразу же задать командную клавишу-акселератор, используя конструктор JMenuitem(string, int). Потом это сделать не удастся, поскольку метод setMnemonic(int) не реализован в классе JMenultem, точнее говоря, он переопределен так, что только выбрасывает исключение. Назначать командную клавишу следует специальным методом setAccelerator(KeyStroke), при этом в пункт меню добавляется описание командной клавиши, например Ctrl+O. Добавим эту командную клавишу к пункту Открыть нашего меню Файл:
open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK O, Event.CTRL MASK));
Класс JMenultem отслеживает, кроме унаследованных событий ChangeEvent, ActionEvent, itemEvent, еще и события классов MenuKeyEvent, происходящие при нажатии и отпускании клавиш, и MenuDragMouseEvent, происходящие при прохождении курсора мыши над пунктом меню.
Пункт меню JCheckBoxMenuItem
Вставить РІ меню РєРЅРѕРїРєРё выбора удобнее всего СЃ помощью специально включенного РІ библиотеку Swing класса JCheckBoxMenuItem. Ртот класс наследует РІСЃРµ свойства своего суперкласса JMenultem Рё добавляет логический метод getstate (), позволяющий отследить состояние РєРЅРѕРїРєРё. Впрочем, можно пользоваться Рё унаследованным методом
isSelected().
Добавим кнопки выбора к меню Вид нашего примера:
JCheckBoxMenuItem cbml = new JCheckBoxMenuItem("Текст");
JCheckBoxMenuItem cbm2 = new JCheckBoxMenuItem("Знaчки");
JCheckBoxMenuItem cbm3 = new JCheckBoxMenuItem("Р РёСЃСѓРЅРєРё");
view.add(cbml); view.add(cbm2); view.add(cbm3);
view.addSeparator();
cbm1.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent e){
if (e.getStateChange() == ItemEvent.SELECTED) ch.setText(txt); else ch.setText("");
}
});
Пункт меню JRadioButtonMenuItem
Для размещения в меню группы радиокнопок удобно воспользоваться классом JRadioButtonMenultem. Он не добавляет функциональности своему суперклассу JMenultem и используется точно так же, как в обычной группе радиокнопок.
Добавим группу радиокнопок к меню Вид нашего примера:
JRadioButtonMenultem rbml =
new JRadioButtonMenuItem("Большой");
JRadioButtonMenultem rbm2 =
new JRadioButtonMenuItem("Средний");
JRadioButtonMenultem rbm3 =
new JRadioButtonMenuItem("Мaлый");
view.add(rbml);view.add(rbm2); view.add(rbm3);
ButtonGroup bg = new ButtonGroup();
bg.add(rbml); bg.add(rbm2); bg.add(rbm3);
Всплывающее меню JPopupMenu
Всплывающее меню (pop-up menu) используется обычно как контекстное меню. Оно появляется в MS Windows и Java L&F при отпускании правой кнопки мыши. В некоторых графических системах для появления контекстного меню надо нажать
среднюю кнопку мыши. Контекстное меню обычно содержит перечень действий, доступных в компоненте, над которым находится курсор мыши, при данном положении курсора мыши. Поэтому контекстное меню не привязывается к строке меню или другому меню, а соотнесено с одним или несколькими компонентами. Оно добавляется к компонентам при обработке событий мыши класса MouseEvent.
Для того чтобы отследить событие, при наступлении которого в данной графической системе следует вызвать всплывающее меню, в классе MouseEvent есть логический метод isPopupTrigger (). К этому методу следует обращаться при всяком событии мыши. Когда метод isPopupTrigger ()вернет true, показав тем самым, что надо вызывать всплывающее меню, следует обратиться к методу show(Component, int, int). В нем первый параметр — это компонент, над которым появится окно всплывающего меню, второй и третий параметры — координаты курсора мыши в системе координат этого компонента. Вот стандартная конструкция, в которой popup — это экземпляр класса JPopupMenu:
public void processMouseEvent(MouseEvent e){ if (e.isPopupTrigger())
popup.show(e.getComponent(), e.getX(), e.getY()); else super.processMouseEvent(e);
}
При этом следует убедиться, что компонент, для которого вызывается всплывающее меню и в котором записан приведенный ранее метод processMouseEvent(), отслеживает события мыши, т. е. к нему присоединен методом addMouseListener( ) хотя бы один слушатель или в нем есть обращение к методу enableEvents (AWTEvent. MOUSE_EVENT_MASK).
Действия СЃРѕ всплывающим меню похожи РЅР° действия СЃ обычным меню. Сначала создается пустое меню конструктором JPopupMenu ( ) или JPopupMenu (String). Строка, записанная РІРѕ втором конструкторе, должна быть заголовком меню, РЅРѕ РѕРЅР° отображается РЅР° экран РЅРµ всеми графическими системами. Рту строку можно добавить потом методом setLabel (String). Затем РІ созданное всплывающее меню методами add(Action), add(JMenultem) или add(String) добавляются пункты меню. Методы insert ( ) Рё remove() позволяют динамически перестроить меню.
Всплывающее меню перед своим появлением на экране, исчезновением с экрана и перед уничтожением вызывает событие класса PopupMenuEvent.
Панель выбора цвета JColorChooser
Многие приложения, связанные с рисованием, обработкой текстов и изображений, требуют задания определенных цветов. Библиотека Swing предлагает простой класс JColorChooser, предоставляющий панель с палитрами цветов в моделях RGB и HSB. Есть три способа использования этого класса.
Самый простой способ — создать диалоговое окно, содержащее панель выбора цветов статическим методом showDialog(Component, String, Color). Первый параметр этого метода задает окно верхнего уровня, в которое помещается панель. Значение null указывает, что окном будет служить экземпляр класса Frame, располагающийся в центре экрана. Второй параметр дает заголовок этому окну, а третий параметр задает начальный цвет, причем null устанавливает белый цвет. Например:
Color c = JColorChooser.showDialog(null, "Цвет", null);
На экране появляется модальное диалоговое окно с цветовой палитрой, ползунками, задающими интенсивность цвета, и кнопками OK, Cancel и Reset. Метод showDialog () возвращает выбранный цвет после щелчка по кнопке OK и null, если щелчок был сделан по кнопке Cancel. После этого диалоговое окно удаляется с экрана, но не уничтожается. Оно запоминает выбранный цвет и при следующем выборе этот цвет можно восстановить щелчком по кнопке Reset. При первом появлении окна на экране щелчок по кнопке Reset возвращает начальный цвет, заданный третьим параметром метода.
Более РіРёР±РєРёР№ Рё сложный СЃРїРѕСЃРѕР± — создать диалоговое РѕРєРЅРѕ СЃ панелью цветов статическим методом createDialog(Component, String, boolean, JColorChooser, ActionListener, ActionListener). Ртот метод, РєСЂРѕРјРµ РѕРєРЅР° верхнего СѓСЂРѕРІРЅСЏ Рё его заголовка, позволяет задать третьим параметром модальность диалогового РѕРєРЅР°. Четвертый параметр обеспечивает выбор цвета РЅРµ только экземпляра класса JColorChooser, РЅРѕ Рё любого расширения этого класса. Пятый Рё шестой параметры позволяют задать нестандартную обработку щелчков РїРѕ кнопкам OK Рё Cancel соответственно. РџСЂРё этом выбранный цвет можно получить методом getColor (). Например:
JDialog d = JColorChooser.createDialog( new JFrame(), "Выбор цвета", false, cc = new JColorChooser(), new OkColor(), new CancelColor());
d.setVisible(true);
class OkColor implements ActionListener{
public void actionPerformed(ActionEvent e){ comp.setColor(cc.getColor());
}
}
class CancelColor implements ActionListener{
public void actionPerformed(ActionEvent e){ comp.setColor(defColor) ;
}
}
Третий способ — если нет необходимости создавать отдельное диалоговое окно, а нужна только панель выбора цветов, то можно воспользоваться конструкто-ром JColorChooser (Color), в котором задается начальный цвет, или конструктором JColorChooser (), устанавливающим белый цвет в качестве исходного.
В этом случае определять момент окончания выбора цвета придется самостоятельно. Класс JColorChooser отслеживает только событие PropertyChangeEvent изменения свойств Java Bean, которое можно обработать, присоединив обработчик унаследованным от класса JComponent методом
addPropertyChangeListener(PropertyChangeListener);
Чтобы отследить выбор цвета, можно также обратиться к модели данных, все интерфейсы и классы которой собраны в пакет j avax. swing. colorchooser.
Текущий выбор цвета хранится РІ модели данных, описанной интерфейсом colorSelectionModel. Класс JColorChooser использует реализацию этого интерфейса классом DefaultColorSelectionModel. Ркземпляр данного класса можно получить методом
getSelectionModel ( ). Рта модель отслеживает событие ChangeEvent.
При необходимости изменения модели данных можно сделать другую реализацию интерфейса ColorSelectionModel и установить ее при создании объекта конструктором
JColorChooser(ColorSelectionModel).
Упражнение
4. Перепишите "рисовалку" листинга 10.9 или листинга 10.10 с использованием компонентов Swing.
Окно выбора файла JFileChooser
Подавляющему большинству приложений приходится работать с файлами. Библиотека Swing имеет в своем составе законченный компонент JFileChooser, соответствующий стандартному окну выбора файла большинства операционных систем.
Для создания экземпляра окна выбора файла есть несколько конструкторов. Конструктор JFileChooser (File) или JFileChooser(String) создает окно, в котором показан каталог с указанным файлом. Конструктор по умолчанию JFileChooser() открывает окно с начальным каталогом пользователя. Он соответствует JFileChooser(null). Еще в трех конструкторах задается объект класса FileSystemView, позволяющий получить различные атрибуты файла.
РџРѕ умолчанию РѕРєРЅРѕ показывает только файлы (режим files_only). Перед выводом РѕРєРЅР° РЅР° экран можно установить режим показа только каталогов directories_only или файлов Рё каталогов files_and_directories. Рти режимы устанавливаются методом
setFileSelectionMode(int).
По умолчанию окно не отображает скрытые (hidden) файлы. Чтобы задать их показ, надо обратиться к методу setFileHidingEnabled (false).
По умолчанию в окне можно отметить один файл. Возможность выбора нескольких файлов задается методом setMultiSelectionEnabled(true).
Фильтр файлов FileFilter
РџРѕ умолчанию РѕРєРЅРѕ показывает РІСЃРµ файлы РІ выбранном каталоге. Установив фильтр, можно ограничить отображение отдельными файлами. Для этого надо расширить абстрактный класс FileFilter РёР· пакета javax.swing.filechooser (РЅРµ перепутайте СЃ интерфейсом FileFilter РёР· пакета java.io) Рё установить полученный фильтр РІ РѕРєРЅРµ выбора файла методом addChoosableFileFilter (FileFilter). Ртот метод можно применить несколько раз СЃ разными параметрами, определив несколько фильтров РІ РѕРґРЅРѕРј РѕРєРЅРµ.
Класс FileFilter содержит всего два абстрактных метода. Логический метод accept (File) возвращает true, если его параметр следует показать в окне. Метод getDescription() возвращает строку описания данного фильтра, которая будет отображена в поле Тип файлов (Files of type) окна выбора.
Вот пример фильтра, отбирающего только файлы с расширением java. Вид окна открытия файла с соответствующим фильтром показан на рис. 11.5.
class JavaFileFilter extends javax.swing.filechooser.FileFilter{ public boolean accept(File f){ if (f != null){
String name = f.getName();
int i = name.lastIndexOf(’.’);
if (i>0 && i < name.length() — 1)
return name.substring(i + 1).equalsIgnoreCase("j ava");
}
return false;
}
public String getDescription(){ return "Файлы Java";
}
}
После создания экземпляра окна выбора и установки режимов и фильтров окно можно показать на экране как модальное окно открытия файла методом showOpenDialog(Component), как модальное окно сохранения файла showSaveDialog(Component) или как модальное окно произвольного выбора showDialog(Component, string). В последнем методе второй параметр задает произвольную надпись вместо надписи Открыть (Open) или Сохранить как (Save as). Первый параметр этих методов задает компонент, от которого зависит и над контейнером которого будет расположено окно выбора файла. Чаще всего это Frame, который можно задать просто параметром null.
Ртак, создать Рё показать РѕРєРЅРѕ выбора файла очень просто:
JFileChooser fch = new JFileChooser();
fch.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); fch.setFileHidingEnabled(false);
fch.setMultiSelectionEnabled(true); fch.addChoosableFileFilter(new JavaFileFilter()); fch.addChoosableFileFilter(new AnotherFileFilter());
switch (fch.showDialog(null, "Открыгть")) { case JFileChooser.APPROVE OPTION:
File selectedFile = fch.getSelectedFile();
File directory = fch.getCurrentDirectory(); break;
case JFileChooser.CANCEL_OPTION: break;
case JFileChooser.ERROR OPTION:
System.err.println("Error"); break;
}
Как получить выбранный файл
Как видно из этого примера, методы вида showXxxDialog () возвращают целое число — одну из трех констант, соответствующих выбору файла и щелчку по кнопке Открыть (Open) или Сохранить (Save) — константа approve_option, щелчку по кнопке Отмена (Cancel) — константа cancel_option или появлению ошибки — константа error_option.
После того как пользователь отметил файл в окне выбора, этот файл можно получить методом getSelectedFile () в виде экземпляра класса File, как показано ранее. Если установлен режим выбора и файлов, и каталогов — files_and_directories, — то при выборе каталога этот метод возвращает null. Если же установлен режим выбора только каталогов directories_only, то возвращается каталог. Если задан выбор нескольких файлов, то их массив типа File[] можно получить методом getSelectedFiles(). Каталог, в котором находится выбранный файл, можно получить в виде экземпляра класса File методом getcurrentDirectory(), как показано ранее.
Ртак, стандартная работа СЃ РѕРєРЅРѕРј выбора файлов выглядит следующим образом:
JFileChooser fch = new JFileChooser(); int state = fch.showOpenDialog(null);
File f = fch.getSelectedFile();
if (f != null && state == JFileChooser.APPROVE OPTION)
JOptionPane.showMessageDialog(null, f.getPath()); else if (state == JFileChooser.CANCEL OPTION)
JOptionPane.showMessageDialog(null, "Canceled");
Разумеется, окно выбора файла не открывает и не сохраняет файл на самом деле, оно только предоставляет этот файл вызвавшей окно выбора программе.
Дополнительный компонент
Возможности окна выбора файла легко расширить, добавив в него произвольный компонент методом setAccessory(JComponent). Чаще всего добавляется небольшое окно предварительного просмотра отмеченного файла, хотя можно добавить что угодно, да- же еще одно окно выбора файла. Вот, например, класс, формирующий окно предварительного просмотра файлов с изображениями:
class ImagePreviewer extends Jlabel implements PropertyChangeListener{
public ImagePreviewer(JFileChooser fch){ if (fch == null)
throw new IllegalArgumentException("fileChooser must be non-null"); fch.addPropertyChangeListener(this);
}
public void loadImageFromFile(File f){
Icon icon = null; if (f != null){
ImageIcon im = new ImageIcon(f.getPath());
Dimension size = getSize();
if (im.getIconWidth() != size.width)
icon = new ImageIcon(im.getImage().getScaledInstance(
size.width, size.height, Image.SCALE DEFAULT)); else icon = im;
}
setIcon(icon);
}
public void propertyChange(PropertyChangeEvent e){
String prop = e.getPropertyName();
if (prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)){
File f = (File)e.getNewValue(); if (isShowing()){
loadlmageFromFile(f); repaint();
}
}
}
} После определения объекта класса JFileChooser, но до его вывода на экран, присоединяем этот компонент:
JFileChooser fch = new JFileChooser();
ImagePreviewer ip = new ImagePreviewer(fch); ip.setPreferredSize(new Dimension(200, 200)); fch.setAccessory(ip); Получившееся окно открытия файла показано на рис. 11.5.
Замена изображений
Р’ РѕРєРЅРµ выбора файла перед каталогами Рё файлами выводятся стандартные значки. РС… можно заменить какими-то РґСЂСѓРіРёРјРё изображениями. Для этого надо расширить абстрактный класс FileView, переопределив пять его методов, Рё установить новые РёР·РѕР±СЂР°- жения методом setFileView(FileView). Например, выведем перед исходными файлами Java изображение дымящейся чашечки кофе:
class JavaFileView extends FileView{
public String getName(File f){
return null; // Оставляем системную реализацию
}
public String getDescription(File f){
String ext = getExtension(f); if (ext != null && ext.equals("java")) return "Рсходным файл Java"; else return null;
}
public String getTypeDescription(File f){ return getDescription(f);
}
Icon icon = new ImageIcon("javacup.gif");
public Icon getIcon(File f){
String ext = getExtension(f); if (ext != null && ext.equals("java")) return icon; return null;
}
public Boolean isTraversable(File f){
return null; // Оставляем системную реализацию
}
protected String getExtension(File f){ if (f != null){
String name = f.getName();
int i = name.lastIndexOf(’.’);
if (i > 0 && i < name.length() — 1)
return name.substring(i + 1).toLowerCase();
}
return null;
}
} Теперь достаточно к описанию объекта fch класса JFileChooser добавить строку
fch.setFileView(new JavaFileView()); и стандартный значок перед исходными файлами Java с расширением java будет заменен изображением из файла javacup.gif, как показано на рис. 11.5. Более содержательный пример создания окна выбора файла имеется в составе J2SE JDK в каталоге $JAVA_HOME/demo/jfc/FileChooserDemo/.
Окно выбора файлов чаще всего создается в отдельном диалоговом окне, но это обычный компонент Swing, и его можно поместить в контейнер. При этом можно убрать кнопки выбора и отмены выбора методом setControlButtonsAreShown(boolean).
Русификация Swing
Как РІРёРґРЅРѕ РёР· примеров этой главы, компоненты библиотеки Swing правильно отображают кириллицу РїРѕ обычным правилам, изложенным РІ главе 5. Остается лишь русифицировать стандартные надписи, что РІРёРґРЅРѕ РЅР° СЂРёСЃ. 11.6. Для этого надо написать несколько файлов ресурсов (properties), которые, если РѕРЅРё есть, просматриваются классами-рисовальщиками компонентов перед выводом РЅР° экран. Рти файлы уже написаны Сергеем Астаховым Рё лежат РІ файле swing_ru.jar, ссылку РЅР° который можно найти, например, РїРѕ адресу . Достаточно поместить архив swing_ru.jar, РЅРµ распаковывая, РІ каталог $JAVA_HOME/lib/ext/, Рё РІСЃРµ надписи Р±СѓРґСѓС‚ сделаны РїРѕ-СЂСѓСЃСЃРєРё, как показано РЅР° СЂРёСЃ. 11.6.
РќР° том же сайте: , РґР° Рё РЅР° РґСЂСѓРіРёС… страницах Рнтернета находится статья Сергея Астахова "Р СѓСЃСЃРєРёРµ Р±СѓРєРІС‹ Рё РЅРµ только...", РІ которой собрано РІСЃРµ касающееся русификации Java. РџРѕ всем вопросам, возникающим РїСЂРё работе СЃ кириллицей, безусловно, нужно обращаться Рє этому ресурсу.
Вопросы для самопроверки
1. Чем отличаются компоненты Swing от компонентов AWT?
2. Какая конструктивная схема использована в компонентах Swing?
3. В каких случаях приходится изменять Модель компонента?
4. В каких случаях приходится изменять Вид компонента?
5. Какими компонентами Swing можно создать кнопку с двумя состояниями?
6. Какие компоненты Swing создают радиокнопки?
7. Как в Swing показать каталоги и имена файлов в виде дерева?
8. Можно ли средствами Swing создать всплывающее меню?
ГЛАВА 12
лучше употреблять тег
Рис. 11.2. Группа радиокнопок и всплывающая подсказка
Рис. 11.3. Ползунок JSlider
Рис. 11.4. Дерево класса JTree
Рис. 11.5. Окно открытия файла
Рис. 11.6. Русифицированное окно открытия файла