Портянкин И. Swing
.pdf
Вывод вспомогательной информации |
235 |
всех рисующих операциях Swing) и настраиваем нужные нам параметры: создаем перо BasicStroke подходящей толщины, включаем сглаживание графики и для удобства немного изменяем переданные нам в метод paintBorder() координаты. Затем мы дважды рисуем четыре кривых Безье: один раз цветом тени, другой раз с небольшим смещением координат основным цветом. Чтобы не повторять идентичный код, мы использовали цикл из двух итераций. Кривые Безье рисуются с помощью класса CubicCurve2D из пакета java.awt.geom, для их прорисовки необходимо указать координаты четырех точек: начала и конца кривой и двух контрольных точек, которые будут отвечать за «искривление» кривой. Контрольные точки мы размещаем на расстоянии одной трети и двух третей от начала кривой, в пяти пикселах влево для первой точки и в пяти пикселах вправо для второй. Это позволяет получить небольшое эффектное искривление. Всего создаются четыре кривые Безье — по одной для каждой из сторон рамки. После завершения рисования необходимо удалить созданный нами объект Graphics методом dispose(), сборщик мусора здесь не поможет, потому что в объекте Graphics используются ресурсы операционной системы, время жизни которых определяем мы сами7.
Для проверки работы нашей новой рамки мы добавили в класс BezierBorder метод main(). В нем (конечно, в потоке рассылки событий) мы создаем окно JFrame, в панель содержимого которого добавляем еще одну панель и устанавливаем для нее рамку с заголовком в качестве основы для нашей новой рамки (заметьте, что новая рамка легко совмещается со стандартными рамками Swing, в том числе и с использованной в примере рамкой с заголовком). В качестве параметров для BezierBorder мы указали зеленый цвет для рамки, темно-серый для тени, а толщину кривых установили равной трем пикселам. Чтобы удостовериться в том, что компоненты Swing легко совмещаются с нашей новой рамкой, мы добавили внутрь панели текстовую область JTextArea. Запустив программу с примером, вы увидите результаты наших трудов.
7 Можно предложить применять интересный вариант кода, позволяющий всегда гарантировать удаление созданного объекта Graphics. Как мы все знаем, всегда вызывается секция finally. Так как исключений при рисовании практически не возникает, секцию catch можно не указывать:
try {
создаем новый объект Graphics, рисуем…
}finally { объект.dispose()
}
236 |
ГЛАВА 8 |
Согласитесь, что новая рамка весьма эффектна, и вряд ли ее можно было бы создать с помощью стандартных рамок Swing. Вы можете добавить ее в свой арсенал и использовать, если вам понадобится особенно пышно выделить некий компонент или их группу.
Рамки и разработка собственных компонентов
Как мы уже обсудили в главе 4, в компонентах Swing нет специального места для рамок, рисующие механизмы библиотеки, скрытые в классе JComponent, прорисовывают рамку прямо на компоненте, вызывая метод paintBorder() после метода paintComponent(). Поэтому, если вы создаете компонент с собственной процедурой прорисовки и не прочь использовать для него рамки Swing, придется учитывать то место, которое занимает рамка (если она есть) и не рисовать на нем. Определить место, занимаемое рамкой, можно и вручную — мы уже обсудили интерфейс Border и знаем, что размеры рамки возвращает метод getBorderInsets(). Получив эти размеры и вычислив, где находится рамка, вы сможете не рисовать на занятом ею месте. Есть и еще один, более простой, способ определить прямоугольник, в котором можно рисовать без риска повредить рамку. Подобный прямоугольник возвращает статический метод getInteriorRectangle() класса AbstractBorder. Рассмотрим небольшой пример и увидим, на что способен этот метод:
//PaintingWithBorders.java
//Рисование компонента с учетом рамки import javax.swing.*;
import javax.swing.border.AbstractBorder; import java.awt.*;
public class PaintingWithBorders extends JFrame { public PaintingWithBorders() {
super("PaintingWithBorders"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//добавим к нашему компоненту рамку
CustomComponent cc = new CustomComponent(); cc.setBorder(BorderFactory.
createTitledBorder("Рамка!"));
//добавим компонент в окно getContentPane().add(cc); setSize(400, 300); setVisible(true);
}
// компонент со своей собственной процедурой прорисовки class CustomComponent extends JComponent {
public void paintComponent(Graphics g) { // получаем подходящий прямоугольник
Rectangle rect = AbstractBorder. getInteriorRectangle(this, getBorder(),
Вывод вспомогательной информации |
237 |
0, 0, getWidth(), getHeight()); // рисуем в нем g.setColor(Color.white);
g.fillRect(
rect.x, rect.y, rect.width, rect.height);
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new PaintingWithBorders(); } });
}
}
В примере мы создаем небольшое окно, в котором размещается специально созданный компонент CustomComponent со своей процедурой прорисовки. Процедура прорисовки, размещенная согласно правилам рисующих механизмов Swing в методе paintComponent(), не делает ничего экстраординарного: она просто закрашивает белым цветом область, занимаемую компонентом. Интересней увидеть, как учитывается место для рамки, которая, как мы знаем, может быть у каждого компонента, унаследованного от базового класса JComponent. Нам помогает метод getInteriorRectangle() класса AbstractBorder: он возвращает прямоугольник Rectangle, в котором можно рисовать, не опасаясь того, что вызванный после paintComponent() метод paintBorder() начнет рисовать рамку прямо на только что прорисованном нами компоненте. Метод getInteriorRectangle() вычисляет, какое пространство занимает рамка (вызывая ее метод getBorderInsets()) и на основе данных, передаваемых ему в качестве параметров (компонент, его рамка, координаты начала области, в которой вы намереваетесь рисовать, а также длина и высота этой области), определяет прямоугольник, прорисовка в котором не затронет рамку. Если рамки у компонента нет, то метод getInteriorRectangle() возвращает размеры компонента без изменений.
Мы устанавливаем для нашего компонента рамку с заголовком TitledBorder, и, запустив программу с примером, вы увидите, что рамка не затрагивает область, залитую белым цветом.
Если бы мы не учли место, занимаемое рамкой, то она появилась бы прямо на компоненте, и внешний вид его был бы безнадежно испорчен. Все стандартные компоненты Swing и их UI-представители учитывают при прорисовке возможность появления рамки, и вам при разработке собственных компонентов или UI-представителей
238 |
ГЛАВА 8 |
следует делать то же самое. Это не слишком элегантно и вносит в процесс прорисовки компонента определенные сложности, но другого пути нет — так уже реализована поддержка рамок в Swing.
Резюме
Вы ничем не ограничены в выводе для пользователя вспомогательной информации, разве что собственной фантазией. Интересы пользователя программы должны быть на первом месте, и, чтобы передать ему нужную информацию в нужное время и в нужном месте, к вашим услугам впечатляющие возможности надписей, всплывающих подсказок и рамок, а также весь арсенал встроенного языка HTML.
Глава 9. Элементы управления
Взгляните на современные приложения с графическим интерфейсом и скажите, какие компоненты интерфейса неизменно присутствуют в каждом из них. Конечно, это элементы управления. В разных обличиях и для разных целей, упакованные в меню, выстроенные в строгие ряды панелей инструментов, они вездесущи. Элементы управления позволяют пользователю общаться с программой и управлять ее ходом.
Количество и внешний вид элементов управления, предоставляемых библиотекой Swing, поражает уже при первом знакомстве. Swing содержит полный арсенал существующих сегодня элементов управления: это кнопки, флажки, переключатели, меню и его элементы и многое другое. Все эти элементы в библиотеке связаны, потому что все они унаследованы от абстрактного класса AbstractButton, определяющего поведение любого компонента, претендующего на звание элемента управления. Таким образом, изучив на примере какого-либо элемента управления важнейшие свойства этого базового класса, мы затем сможем с легкостью применять полученные знания и ко всем остальным элементам управления, разом освоив приличную часть библиотеки.
Основные свойства, присущие любому элементу управления Swing, проще всего изучать на примере компонента, который уже не раз встречался нам в примерах и выручал своей простотой, — это кнопка JButton.
Кнопки JButton
В простейшем виде кнопки — это самые обычные прямоугольники с текстом; пользователь щелкает на них мышью, чтобы выполнить какое-либо действие или о чем-либо просигнализировать. Кнопки JButton хороши еще и тем, что кроме собственного внешнего вида,
вних практически нет ничего уникального — все в них унаследовано от класса AbstractButton, и поэтому все, что верно для них, будет верно и для остальных элементов управления.
Чаще всего использование этого компонента сводится к его созданию, размещению
вподходящем месте контейнера и привязыванию слушателя событий. Выглядит все обычно так:
JButton button = new JButton("Кнопка"); button.addActionListener(new ButtonAction());
Таким образом, время занимает не столько создание и настройка кнопки, сколько размещение ее в контейнере и написание обработчика событий. Впрочем, так оно и должно быть — кнопка призвана всего лишь дать сигнал о начале некоторого действия. Начнем знакомство с кнопками с того, что узнаем, как можно менять внешний вид кнопок, используя значки, цвета и различное выравнивание их содержимого.
Внешний вид кнопок
Пожалуй, в библиотеке Swing нет больше других компонентов, облик которых можно так же легко изменить, как облик кнопок, не меняя менеджера внешнего вида и поведения и не переопределяя рисующих методов. С кнопками можно делать практически все — сопоставлять каждому движению пользователя свой значок, убирать рамку, закрашивать в любой цвет, перемещать содержимое по разным углам, не рисовать фокус.
240 |
ГЛАВА 9 |
И это выполняется с такой фантастической простотой (достаточно вызвать один-два метода), что в творческом порыве можно забыть буквально обо всем. Рассмотрим пример, в котором будут созданы кнопки самых необычных форм и размеров, а затем обсудим, как это делается:
//ButtonStyles.java
//Изменение внешнего вида кнопок JButton
//с помощью значков, цветов, рамок и т. п. import javax.swing.*;
import java.awt.*;
public class ButtonStyles extends JFrame { public ButtonStyles() {
super("ButtonStyles"); setDefaultCloseOperation( EXIT_ON_CLOSE );
//используем последовательное расположение setLayout(
new FlowLayout( FlowLayout.LEFT, 10, 10));
//самая простая кнопка
JButton button = new JButton("Обычная кнопка"); add(button);
//кнопка со значками на все случаи жизни button = new JButton();
button.setIcon(new ImageIcon("images/b1.gif")); button.setRolloverIcon(
new ImageIcon("images/b1r.gif")); button.setPressedIcon(
new ImageIcon("images/b1p.gif")); button.setDisabledIcon(
new ImageIcon("images/b1d.gif"));
//для такой кнопки лучше убрать
//все ненужные рамки и закраску button.setBorderPainted(false); button.setFocusPainted(false); button.setContentAreaFilled(false); add(button);
//кнопка с измененным цветом и HTML-текстом button = new JButton(
"<html><h2><font color=\"yellow\">Зеленая кнопка"); button.setBackground(Color.green);
add(button);
//изменение выравнивания текста и изображения
button = new JButton("Изменение выравнивания",
Элементы управления |
241 |
new ImageIcon("images/button.gif")); button.setMargin(new Insets(10, 10, 10, 10)); button.setVerticalAlignment(SwingConstants.TOP); button.setHorizontalAlignment(SwingConstants.RIGHT); button.setHorizontalTextPosition(SwingConstants.LEFT); button.setVerticalTextPosition(SwingConstants.BOTTOM); button.setIconTextGap(10);
//сделаем кнопку большой, чтобы увидеть выравнивание button.setPreferredSize(new Dimension(300, 100)); add(button);
//отключенная кнопка
button = new JButton("Выключено"); button.setEnabled(false); add(button);
// выводим окно на экран setSize(400, 350); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new ButtonStyles(); } });
}
}
В этом примере создается небольшое окно, в котором используется менеджер последовательного расположения FlowLayout. Такое расположение гарантированно придаст кнопкам предпочтительный размер. Далее в окно помещается несколько кнопок, каждая из которых демонстрирует свой способ изменения внешнего вида.
Первой в контейнер помещается самая обычная кнопка, которая уже встречалась нам в примерах, просто для того, чтобы можно было сравнить ее с остальными кнопками. Затем следует кнопка, на которой вместо текста располагаются значки, причем на «все случаи жизни». Для задания значков применяются свойства, перечисленные в табл. 9.1.
Таблица 9.1. Свойства, позволяющие задавать значки для кнопок
Свойства |
Описание |
(и методы get/set) |
|
icon |
Используется для установки «повседневного» значка, который будет ви- |
|
ден всегда, когда кнопка доступна |
rolloverIcon |
С помощью этого свойства можно установить значок для получения |
|
эффекта «наведения мыши» — когда указатель мыши оказывается на |
|
кнопке, появляется этот значок. Обычно в этом случае используется тот |
|
же значок, только к нему добавляется эффект объема или изменяется |
|
цвет части значка (как в нашем примере) |
242 |
ГЛАВА 9 |
Таблица 9.1 (продолжение) |
|
|
|
Свойства |
Описание |
(и методы get/set) |
|
pressedIcon |
Используется для установки значка, который выводится на экран при |
|
щелчке пользователем на кнопке (значок остается на экране до тех |
|
пор, пока кнопка мыши не отпущена и указатель находится в области |
|
кнопки) |
disabledIcon |
Если вы задаете значок посредством данного свойства, то, когда кноп- |
|
ка будет отключена с помощью метода setEnabled(false), появится |
|
специальный значок. Если специального значка нет, а обычный значок |
|
есть, то в качестве значка для отключенной кнопки будет использована |
|
черно-белая копия обычного значка (аналогично поведению надписи |
|
JLabel, которую мы подробно рассмотрели в главе 6) |
Если вывести такую кнопку на экран «как есть», она будет выглядеть, мягко говоря, не очень хорошо, потому что у нее останутся все свойства, присущие обычной кнопке с текстом: будет рисоваться рамка, при наличии фокуса появится контур, а при нажатии она будет закрашиваться темным цветом. Именно для того чтобы убрать эти эффекты, в примере задействуются методы setBorderPainted(), setFocusPainted() и setContentAreaFilled(). Первый из перечисленных методов позволяет отключить прорисовку рамки (то же самое можно сделать и с помощью вызова setBorder(null), но тогда вернуть кнопке ее рамку не удастся), второй отключает прорисовку специального контура, проявляющегося, если кнопка обладает фокусом ввода, а третий дает возможность отключить закраску кнопки в нажатом состоянии. Однако работать с такой кнопкой гораздо сложнее: будет непонятно, выбрана кнопка или нет, где она начинается и т. п. Поэтому мы рекомендуем быть осторожнее и применять эти методы только в тех приложениях, в которых весь интерфейс основан на разнообразных изображениях (например, в играх).
После этого в контейнер добавляется еще одна простая кнопка с текстом, для которой методом setBackground() устанавливается необычный цвет заполнения. В некоторых ситуациях бывает полезно просто изменить цвет кнопки, и вы видите, что сделать это действительно несложно. Помните только, что цвет заполнения изменится, только если у кнопки включено свойство непрозрачности (opaque). Все стандартные внешние виды, поставляемые со Swing, делают кнопки непрозрачными, но внешние виды от стороннего производителя могут иметь на это свою «точку зрения», так что иногда при использовании стороннего внешнего вида приходится вручную устанавливать для свойства непрозрачности значение true, если вы хотите закрасить кнопку в особенный цвет.
После прочтения главы 8 мы в полной мере смогли оценить мощь и гибкость встроенного в надписи и подсказки языка HTML, кнопки в этом отношении нас также не разочаровывают. Как и все остальные компоненты библиотеки, выводящие на экран некоторый текст, они поддерживают HTML, что вместе с широчайшими возможностями по настройке расположения содержимого и по управлению всеми аспектами внешнего вида кнопок дает вам неограниченную власть над видом вашего приложения. В примере мы парой строк кода смогли без особого напряжения создать кнопку, буквально «пылающую» яркими цветами и выделяющуюся нестандартными размерами. Не обладай кнопки поддержкой встроенного языка HTML, такой эффект дался бы нам гораздо большими усилиями, а про остальные возможности HTML (сразу же можно вспомнить незаменимый тег <br>, позволяющий легко организовать в компонентах Swing многострочные надписи) и вовсе пришлось бы забыть.
Далее показано, как и какими методами можно изменить положение содержимого кнопки (значка или надписи). Правда, чтобы увидеть это, пришлось придать кнопке
Элементы управления |
243 |
неестественно большой размер, потому что если оставить тот размер, который задан по умолчанию, менеджер FlowLayout придаст кнопке оптимальный размер, и как ни меняй после этого расположение содержимого, мы все равно ничего бы не увидели. Если же кнопка занимает больше пространства, чем ей надо, вопросы положения содержимого становятся актуальными.
Для демонстрации вариантов выравнивания была создана кнопка и с текстом, и со значком1 (как вы можете видеть из кода программы, для этого случая у класса JButton даже имеется специальный конструктор). В табл. 9.2 описаны свойства, которые помогли нам добиться желаемого визуального эффекта.
Таблица 9.2. Свойства, позволяющие настроить содержимое кнопок JButton
Свойства |
Описание |
(и методы get/set) |
|
margin |
Это свойство позволяет управлять размером полей, которые от- |
|
деляют содержимое кнопки (текст и значок) от ее границ. Как мы |
|
уже отмечали в главе 5, при использовании внешнего вида Metal |
|
рекомендуются поля в 12 пикселов |
verticalAlignment |
Данная пара свойств используется для изменения позиции все- |
|
го содержимого кнопки (и значка, и текста) относительно границ |
horizontalAlignment |
кнопки. С их помощью поместить это содержимое можно в лю- |
|
бой из углов кнопки или в ее центр (по умолчанию оно там и на- |
|
ходится). В нашем примере текст и значок помещаются в верх- |
|
ний правый угол кнопки. Полный список констант, управляющих |
|
положением содержимого, можно найти в интерактивной доку- |
|
ментации для класса SwingConstants, доступной на сайте java. |
|
sun.com |
horizontalTextPosition |
С помощью этих свойств вы можете управлять положением текста |
|
кнопки относительно ее значка (если он есть, конечно) по горизон- |
verticalTextPosition |
тали и вертикали. Использование в примере первого метода по- |
|
зволило разместить надпись не справа от значка, а слева2. Второй |
|
метод позволил поместить надпись ниже значка |
iconTextGap |
Это свойство долго отсутствовало в классе AbstractButton, хотя |
|
необходимость в нем была (например, в классе JLabel, имеющем |
|
аналогичный набор свойств для выравнивания содержимого, оно |
|
появилось вместе с появления библиотеки Swing). Свойство позво- |
|
ляет изменить расстояние между значком и текстом. |
Нетрудно увидеть, что содержимое кнопок и расположение в них значка относительно текста задается набором свойств, который полностью аналогичен набору свойств для выравнивания содержимого надписи JLabel. Мы обсуждали надписи в главе 8, поэтому большая часть возможностей кнопок должна быть вам прекрасно известна.
Наконец, самой последней в контейнер добавляется еще одна кнопка без всяких изысков, она показывает, как кнопку можно отключить. Делается это методом setEnabled(), этим же методом кнопка включается.
1 Этот симпатичный значок автор взял из специальной коллекции стандартной графики для Java-приложений, выполненных во внешнем виде Metal. Эта коллекция бесплатно распространяется фирмой Sun/Oracle, найти ее можно на сайте java.sun.com.
2 Кстати, использовать константы RIGHT (справа) и LEFT (слева) для выравнивания содержимого в приложениях, которые затем может понадобиться локализовать в странах с нестандартным письмом, не рекомендуется.
244 |
ГЛАВА 9 |
Запустив программу с примером, вы сможете убедиться, что внешний вид кнопок действительно меняется.
Конечно, в рассмотренном примере нет никаких хитростей, и все очень просто. Но так и должно быть — с каждой строкой кода вы создаете что-то действительно стоящее, а не решаете в очередной раз проблему рисования значка на кнопке. Библиотеки созданы для того, чтобы упростить работу программиста, вобрав в себя наиболее востребованные и сложные операции, и в этом библиотека Swing несомненно преуспела. И не забывайте о том, что все только что рассмотренные способы изменения внешнего вида действуют на все элементы управления библиотеки Swing, которые мы будем рассматривать в этой главе.
У кнопок есть модель
Удивительно, но у таких простых компонентов как кнопки есть собственная, самая настоящая, модель, хранящая состояние кнопки (поддержка модели описана в базовом классе AbstractButton, так что модель имеется у всех обсуждаемых в этой главе элементов управления). В главе 1 при обсуждении архитектуры MVC, используемой в Swing, мы выяснили, что модель хранит данные компонента, а отображает эти данные его UI-представитель. Именно так все работает для кнопок: в модели ButtonModel хранится информация о состоянии кнопки (нажата ли она, находится ли над ней указатель мыши, в выбранном ли она положении; даже символ мнемоники и группа, к которой может принадлежать кнопка, хранятся в модели), а ее UI-представитель получает эту информацию из модели
исоответствующим образом прорисовывает кнопку. Более того, все события, которые поддерживают кнопки и другие элементы управления (мы их вскоре обсудим подробнее
ирассмотрим пример), на самом деле генерируются в модели кнопок ButtonModel (к при-
меру, если кнопка нажата, и на ней отпускается кнопка мыши, модель генерирует событие ActionEvent). С другой стороны, внешний вид кнопки (текст, значок, выравнивание) в модели не хранится, все это находится уже в конкретных классах, таких как JButton, так что модель отвечает лишь за состояние кнопки, но не за ее внешний вид.
«Зачем же кнопкам нужна отдельная модель?» — спросите вы. На самом деле, кнопки настолько просты и незатейливы, что отдельная модель кажется для них излишней роскошью. Вы в большинстве своих программ вряд ли используете эту модель отдельно от кнопок — все будет сводиться к простому созданию компонента и размещению его в контейнере. Тем не менее, у модели кнопок есть свои преимущества. Представим, что нам нужно
