Оформление ГИП компонентами Swing
Стандартная поставка 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, автоматически загружающийся с сайта http://www.oracle.com/technetwork/java/index-jsp-141438.html/ при загрузке апплета, использующего классы библиотеки Swing. Модуль Java Plug-in входит в состав JRE и автоматически подгружается в браузер, работающий там, где установлен пакет JRE. Нужно лишь, чтобы браузер распознавал тег <object> или <embed>, в котором указан класс апплета.
В состав 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. На сайте http://java.sun.com/products/jlf/ есть подробнейшее руководство по созданию графического интерфейса пользователя в стиле Java L&F — "Java Look and Feel Design
Guidelines". Разумеется, в этой книге мы не сможем полностью рассмотреть все возможности библиотеки Swing, но ее структуру и основные средства освоим в той мере, которая позволит создать удобное и красивое приложение, приятное для работы. Изложение библиотеки Swing в этой части книги рассчитано на то, что читатель знаком с постоянно обновляемым электронным учебником "The Java Tutorial. A practical guide for programmers", расположенным по адресу http://download.oracle.com/javase/ 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("<html>Первая <font coloг="red">строка<p>вторая");
Внимание!
Тег <html> должен начинать строку, идти сразу же после открывающей кавычки, без пробелов.
При этом следует учитывать, что размер компонента может быть вычислен неправильно и проинтерпретированный текст не поместится в компоненте. Поэтому, например, вместо тега <br> лучше употреблять тег <p>.
Еще одно интересное свойство. Компонент JLabel можно связать с другим компонентом методом setLabelFor(Component). Затем с какой-либо буквой надписи, например А, нужно связать командную клавишу методом
setDisplayedMnemonic(’A’);
или методом
setDisplayedMnemonic(KeyEvent.VK A);
Второй из этих методов требует включения в программу пакета j ava. awt. event. Буква a в надписи будет подчеркнута. После этого нажатие комбинации клавиш <Alt>+<A> вызовет передачу фокуса связанному с надписью компоненту (на компонент JLabel фокус никогда не передается). В тексте HTML буква, связанная с командной клавишей, не подчеркивается автоматически, для нее надо задать подчеркивание тегом <u>.
Если в надписи несколько одинаковых букв, то будет подчеркнута первая из них. Методом 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(,,<html><u>Д</u>алее,,, 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("<html><u>Д</u>а?<p>Нет?"); 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 ( "<html><u>R</u>расный<p>фон,,); rb1. setMnemoni c (KeyEvent. VK R);
rb1.setToolTipText("<html>E^i выбираете<p>красный фон");
rb1.addActionListener(this); rb1.setActionCommand("red");
JRadioButton rb2 =
new JRadioButton (XhtmlXuX^u>еленый<p>фон,,);
rb2. setMnemoni c (KeyEvent .VK P);
rb2.setToolTipText("<html>Вы выбираете^Хеленый фон"); rb2.addActionListener(this); rb2.setActionCommand("green");
JRadioButton rb3 =
new JRadioButton ( XhtmlXuXX/u>иний<p>фон" ); rb3.setMnemoni c(KeyEvent.VK C);
rb3.setToolTipText("<html>Вы выбираете<p>синий фон"); 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);
Если в списке больше элементов, чем выделено строк этим методом, то в раскрывающемся окне появится полоса прокрутки.
При выборе элемента или окончании редактирования (нажатии клавиши <Enter>) в раскрывающемся списке происходит событие класса ActionEvent и одно или два события класса ItemEvent, а при раскрытии и свертывании списка — событие класса PopupMenuEvent. Обработчик события может получить выбранный элемент методом
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 = {"Иванов", "<html><font color=red>Петров", "Сидоров"};
JList list = new JList(data);
list.setVisibleRowCount(2);
list.addListSelectionListener(this);
JScrollPane sp = new JScrollPane(list); f.getContent Pane().add(sp);
Так же, как и в раскрывающийся список JComboBox, в список JList можно занести не только текст, но и изображения.
По умолчанию в списке можно выбрать любое число любых элементов, держа клавишу <Ctrl> нажатой. После применения метода
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.
При всяком изменении текущего значения, а также окончании ввода в поле нового значения путем нажатия клавиши <Enter>, в счетчике происходит событие класса ChangeEvent. Поэтому получать значения счетчика надо примерно так:
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("<html><u>0</u>aRn"));
JMenu edit = new JMenu("<html><u>n</u>paBKa"));
JMenu view = new JMenu("<html><u>B</u>HJ"));
JMenu help = new JMenu("<html><u>C</u>npaBKa"));
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, ссылку на который можно найти, например, по адресу http://lib.juga.ru/artide/artideprint/125/-1/53/. Достаточно поместить архив swing_ru.jar, не распаковывая, в каталог $JAVA_HOME/lib/ext/, и все надписи будут сделаны по-русски, как показано на рис. 11.6.
На том же сайте: http://lib.juga.ru/artide/archive/19/, да и на других страницах Интернета находится статья Сергея Астахова "Русские буквы и не только...", в которой собрано все касающееся русификации Java. По всем вопросам, возникающим при работе с кириллицей, безусловно, нужно обращаться к этому ресурсу.
Вопросы для самопроверки
1. Чем отличаются компоненты Swing от компонентов AWT?
2. Какая конструктивная схема использована в компонентах Swing?
3. В каких случаях приходится изменять Модель компонента?
4. В каких случаях приходится изменять Вид компонента?
5. Какими компонентами Swing можно создать кнопку с двумя состояниями?
6. Какие компоненты Swing создают радиокнопки?
7. Как в Swing показать каталоги и имена файлов в виде дерева?
8. Можно ли средствами Swing создать всплывающее меню?
ГЛАВА 12