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

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

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

Вывод вспомогательной информации

225

слушателя mouseEntered()), класс ToolTipManager запускает таймер, при срабатывании которого (если при этом не происходит движение мыши, иначе таймер запускается заново) на экран выводится подсказка. Подсказка создается в два этапа. Собственно компонентподсказка JToolTip создается методом createToolTip(), имеющимся в любом компоненте Swing. Другой компонент (тот, что будет «всплывать» над остальными компонентами, в нем размещается подсказка JToolTip) создается с помощью вспомогательного класса PopupFactory. Этот класс определяет тип компонента (легковесный или тяжеловесный) и размещает его в нужном месте экрана (в слое POPUP_LAYER многослойной панели или в новом окне без рамки JWindow). Общую картину иллюстрирует рис. 8.1.

Компонент

 

ToolTipManager

 

PopupFactory

 

 

 

 

 

 

 

 

 

 

 

 

setToolTipText()

registerComponent()

mouseEntered()

enterTimer.start()

showTipWindow()

createToolTip()

getPopup()

Подсказка на экране

Рис. 8.1. Схема вывода всплывающей подсказки

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

УвасестьвозможностьуправлятьнекоторымиаспектамиработыклассаToolTipManager. Прежде всего, вы можете переопределить метод createToolTip() в вашем компоненте, если вам понадобится предоставить для него какую-то особенную подсказку, но делать это слишком часто не рекомендуется. Лучше, если у приложения имеется единый продуманный облик, и подсказки в него органично вписываются. Если вас не устраивают подсказки, предоставляемые используемым вашим приложением внешним видом, лучше всего написать для них новый UI-представитель. С другой стороны, имеются стандартные варианты настройки подсказок, и мы их сейчас рассмотрим.

Настройка подсказок

Класс ToolTipManager позволяет настраивать параметры вывода подсказок на экран. В первую очередь это относится ко времени, которое проходит перед появлением подсказки, времени ее нахождения на экране и еще к некоторым параметрам, позволяющим тонко управлять всплывающими подсказками в вашем приложении. Параметры,

226

ГЛАВА 8

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

//ToolTipsTuning.java

//Настройка подсказок import javax.swing.*;

public class ToolTipsTuning extends JFrame { public ToolTipsTuning() {

super("ToolTipsTuning"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//добавим кнопки с подсказками

JPanel contents = new JPanel(); JButton b1 = new JButton("Первая");

b1.setToolTipText("Подсказка для первой"); JButton b2 = new JButton("Вторая"); b2.setToolTipText("Подсказка для второй"); contents.add(b1);

contents.add(b2);

//настройка подсказок

ToolTipManager ttm = ToolTipManager.sharedInstance();

ttm.setLightWeightPopupEnabled(false);

ttm.setInitialDelay(1000);

ttm.setDismissDelay(500);

ttm.setReshowDelay(1000); // выводим окно на экран add(contents); setSize(200, 100); setVisible(true);

}

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

new Runnable() {

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

}

}

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

Вывод вспомогательной информации

227

Таблица 8.2. Свойства класса ToolTipManager

Свойство

Описание

lightWeightPopupEnabled

Рекомендует классу ToolTipManager использовать для подсказок

 

легковесные компоненты, которые затем появятся в слое POPUP_

 

LAYER многослойной панели. Если значение равно false, подсказ-

 

ки размещаются либо в тяжеловесных компонентах (если места

 

в окне достаточно), либо в собственных окнах без рамки (когда

 

места не хватает). Следит за этим фабрика классов PopupFactory,

 

предназначенная для всплывающих окон. Это свойство может

 

быть полезно, если вы совмещаете в одном окне тяжеловесные

 

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

 

весные подсказки могут быть скрыты тяжеловесными компонен-

 

тами

initialDelay

Задержка (в миллисекундах) между остановкой указателя мыши

 

на компоненте и появлением подсказки

dismissDelay

Время, в течение которого подсказка остается на экране (если

 

только не происходит каких-то других событий, например, потери

 

окном фокуса)

reshowDelay

Время, в течение которого действует «режим подсказок». Если

 

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

 

переводит указатель мыши на другой компонент, подсказка для

 

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

Впримере мы использовали довольно необычные значения описанных свойств,

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

Рамки

Рамки (borders) библиотеки Swing позволяют визуально упорядочить пользовательский интерфейс, разделяя компоненты по их функциям и назначению. Также довольно часто они используются в качестве «украшения» для того или иного компонента библиотеки, и нередко именно рамка определяет особый колорит компонента. Если вы вспомните внешний вид кнопок JButton, специальные границы текстовых полей или панелей прокрутки, то убедитесь, что основу внешнего вида таких компонентов иногда составляет необычная рамка.

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

228

ГЛАВА 8

Поддержка рамок обеспечивается рисующими механизмами базового класса JComponent (детали мы исследовали в главе 4), так что для любого компонента Swing вы можете использовать нужную вам рамку. Рамка хранится в качестве свойства border, сменить или получить ее позволяют методы get/set. Обязанности рамок в Swing описаны в интерфейсе Border из пакета javax.swing.border, в этом же пакете находится впечатляющее количество стандартных рамок. Давайте рассмотрим пример и ознакомимся

сними:

//Borders.java

//Рамки Swing import javax.swing.*;

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

public class Borders extends JFrame { public Borders() {

super("Borders"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создаем панели с всевозможными рамками

JPanel contents = new JPanel( new GridLayout(3, 2, 5, 5));

contents.add(createPanel(new TitledBorder( "Рамка с заголовком"), "TitledBorder"));

contents.add(createPanel(new EtchedBorder(), "EtchedBorder"));

contents.add(createPanel(new BevelBorder( BevelBorder.LOWERED), "BevelBorder"));

contents.add(createPanel(new SoftBevelBorder( BevelBorder.RAISED), "SoftBevelBorder"));

contents.add(createPanel(new LineBorder( Color.BLACK, 5), "LineBorder")); contents.add(createPanel(new MatteBorder(

new ImageIcon("matte.gif")), "MatteBorder"));

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

add(contents);

pack();

setVisible(true);

}

// метод создает панель с рамкой и надписью private JPanel createPanel(Border b, String text) {

JPanel panel = new JPanel(new BorderLayout()); panel.add(new JLabel(text)); panel.setBorder(new CompoundBorder(

Вывод вспомогательной информации

229

b, new EmptyBorder(30, 30, 30, 30))); return panel;

}

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

new Runnable() {

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

}

}

В примере мы создаем окно с панелью в центре, для которой выбираем табличное расположение с шестью ячейками; именно шесть наиболее употребительных рамок содержит пакет javax.swing.border. В ячейки табличного расположения мы добавляем панели с установленными рамками и надписью JLabel, которая помогает разобраться, какой класс позволяет создать ту или иную рамку. При этом используется вспомогательный метод createPanel(), который создает новую панель с полярным расположением, в центр ее добавляет надпись и устанавливает нужную рамку Border. Заметьте, что метод createPanel() «украшает» панели с помощью сразу трех рамок: собственно панель получает в качестве рамки объект CompoundBorder, позволяющий совместить две рамки в одну. Внешней рамкой является та, которая передается в метод в качестве параметра, а внутренняя рамка для всех панелей одна — это рамка EmptyBorder, оставляющая между границей панели и ее содержимым пустое пространство, чтобы надпись с названием рамки было проще читать. Запустив программу с примером, вы сможете насладиться разнообразием рамок Swing, и это только начало: каждая из рассмотренных нами шести рамок имеет дополнительные варианты оформления.

230

ГЛАВА 8

Когда вы будете выбирать рамку для своего приложения, вам сможет помочь табл. 8.3 с их краткими характеристиками.

Таблица 8.3. Стандартные рамки Swing

Название рамки

Назначение

TitledBorder

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

 

рамке находится. При этом текст может находиться на любой стороне рам-

 

ки и иметь различные варианты выравнивания. Очень часто используется

 

в интерфейсах

LineBorder

Одна из самых простых, но наиболее часто используемых рамок. Рисует

 

рамку линией определенного цвета, толщину линии можно выбрать по вку-

 

су, позволяет скруглять углы

EmptyBorder

Отличный помощник в создании выверенных пользовательских интер-

 

фейсов, позволяет окружить компонент пустыми полями со всех четырех

 

сторон. Мы уже использовали эту рамку в главе 5

BevelBorder

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

 

можно настроить цвета, требуемые для получения объемных эффектов.

 

Имеет «сильный» визуальный эффект, поэтому использовать ее надо

 

осторожно

SoftBevelBorder

Эта рамка аналогична предыдущей (унаследована от нее), обладает теми

 

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

EtchedBorder

Рамка с тиснением (в стиле чеканки по металлу) выглядит скромно, но

 

эффектно, поэтому используется часто. Может быть вогнутой или вы-

 

пуклой

CompoundBorder

Позволяет совместить две рамки и избавляет от утомительного совмеще-

 

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

 

произвольное количество

MatteBorder Очень полезная и мощная рамка, позволяющая использовать узор из значков Icon. С ее помощью легко создавать очень необычные рамки. Она

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

иновые рамки

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

итяжелым для восприятия. Лучше ограничиться одной–двумя рамками для четкого разделения компонентов, выполняющих разные функции или собирающих данные разного предназначения, и никогда не следует вкладывать рамки друг в друга, если только это не относится к рамке EmptyBorder. Для организации компонентов интерфейса лучше всего использовать рамку TitledBorder, размещающую на других рамках текст, для оформления компонентов хорошо подходят неброские рамки LineBorder

иEtchedBorder. Если вам понадобится особенно красочная рамка, вы можете обратиться к MatteBorder.

Всегда стоит держать под рукой рамку с пустым пространством EmptyBorder. Она не

вносит в интерфейс излишней пестроты и позволяет хорошо организовать его. В главе 7 мы вкратце рассмотрели рекомендации по созданию пользовательских интерфейсов для Java -приложений, которые проще всего выполнить именно с помощью рамок EmptyBorder, особенно это касается отступов от границ окон и панелей.

Вывод вспомогательной информации

231

Фабрика BorderFactory

Мы уже узнали, как напрямую создавать рамки из пакета javax.swing.border. Оказывается, есть еще один способ создания рамки — использовать фабрику javax.swing.BorderFactory. Это класс с набором статических методов, создающих тот или иной тип рамки. Он позволяет отказаться от импорта пакета javax.swing.border, а также по мере возможности кэширует создаваемые объекты Border. На самом деле, компоненты Swing прорисовываются поочередно, один за другим (как вы помните, запрос на прорисовку приходит из очереди событий), и это позволяет использовать один и тот же объект Border для разных компонентов. Правда, успешно кэшировать можно лишь рамки без состояния, к которым относятся объемные рамки BevelBorder и SoftBevelBorder, а также рамка EtchedBorder. Остальные рамки отличаются друг от друга цветами, надписями и другими параметрами и создаются по одной на компонент. А теперь убедимся, что использовать класс

BorderFactory не сложно:

//UsingBorderFactory.java

//Фабрика рамок BorderFactory import javax.swing.*;

public class UsingBorderFactory extends JFrame { public UsingBorderFactory() {

super("UsingBorderFactory"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//рамка для панели содержимого

JPanel cp = (JPanel)getContentPane(); cp.setBorder(BorderFactory.createTitledBorder(

BorderFactory.createRaisedBevelBorder(), "Сделано на фабрике рамок"));

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

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

}

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

new Runnable() {

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

}

}

Фабрика BorderFactory обеспечивает создание рамки с заголовком для панели содержимого нашего окна. Как видите, рамку любого типа можно создать соответствующим статическим методом, а рамку с заголовком — на основе рамки другого типа.

Создание рамок напрямую или с помощью фабрики классов BorderFactory практически одинаково, что выбрать — дело вкуса. Класс BorderFactory имеет преимущество, лишь когда в вашем приложении создается много рамок, которые могут кэшироваться, таких как BevelBorder или EtchedBorder. В этом случае следует предпочесть именно его.

232

ГЛАВА 8

Создание собственных рамок

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

Для создания новой рамки необходимо реализовать интерфейс javax.swing.border. Border, в котором совсем немного методов6. Во-первых, вам нужно определиться в том, будет ли ваша рамка непрозрачна (opaque). Смысл свойства непрозрачности для рамок соответствует его смыслу для других компонентов Swing: если вы утверждаете, что ваша рамка непрозрачна, то обязуетесь закрашивать всю область, занимаемую рамкой. Это упрощает работу механизмов рисования. Во-вторых, надо определить размеры рамки (как вы помните из главы 4, рамка в Swing рисуется прямо поверх компонента, и последнему надо знать ее размеры, чтобы его части не оказались закрытыми рамкой). Ну и последним этапом является собственно прорисовка рамки в специальном методе paintBorder(). А теперь займемся непосредственно процессом создания рамки: попробуем создать рамку, составленную из эффектных кривых Безье:

//BezierBorder.java

//Рамка, составленная из кривых Безье import javax.swing.*;

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

import java.awt.geom.*;

public class BezierBorder implements Border { // свойства рамки

private Color color, shadow; private float thickness;

// значения свойств передаются в конструкторе public BezierBorder(Color color, Color shadow,

float thickness) { this.color = color;

this.shadow = shadow; this.thickness = thickness;

}

// место, занимаемое рамкой

6 Есть еще один способ создать собственную рамку — унаследовать ее от абстрактного класса AbstractBorder. Но такой способ вряд ли значительно снизит объем работ по созданию рамки, особенно если рамка весьма необычна, так что мы выбираем реализацию интерфейса Border.

Вывод вспомогательной информации

233

public Insets getBorderInsets(Component comp) { return new Insets(9, 9, 9, 9);

}

//наша рамка частями прозрачна public boolean isBorderOpaque() {

return false;

}

//метод прорисовки рамки

public void paintBorder(Component c, Graphics g,

int x, int y, int width, int height) {

//используем новый объект Graphics Graphics2D g2 = (Graphics2D)g.create();

//настройка пера, координат и цвета g2.setStroke(new BasicStroke(thickness)); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,

RenderingHints.VALUE_ANTIALIAS_ON);

x += 5; y += 5; width -= 10; height -= 10; g2.setColor(shadow);

//прорисовываем тень и рамку

for (int i=0; i<2; i++) {

CubicCurve2D left = new CubicCurve2D.Double( x, y, x-5, y+height*1/3, x+5, y+height*1/3,

x, y+height);

CubicCurve2D right = new CubicCurve2D.Double( x+width, y, x+width-5, y+height*1/3, x+width+5, y+height*2/3, x+width, y+height);

CubicCurve2D top = new CubicCurve2D.Double( x, y, x+width*1/3, y+5, x+width*2/3, y-5, x+width, y);

CubicCurve2D bottom = new CubicCurve2D.Double( x, y+height, x+width*1/3, y+height+5, x+width*2/3, y+height+5, x+width, y+height);

g2.draw(left);

g2.draw(right);

g2.draw(top);

g2.draw(bottom);

// на втором шаге рисуем саму рамку x--; y--; width--; height--; g2.setColor(color);

}

g2.dispose();

}

234 ГЛАВА 8

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

new Runnable() { public void run() {

JFrame frame = new JFrame("BezierBorder"); // создаем панель с нашей рамкой

JPanel p = new JPanel(new BorderLayout()); Border b = new TitledBorder(new BezierBorder(

Color.GREEN, Color.DARK_GRAY, 3f), "Bezier"); p.setBorder(b);

p.add(new JTextArea()); // выводим окно на экран

frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);

frame.add(p); frame.setSize(200, 200); frame.setVisible(true);

} });

}

}

Класс BezierBorder реализует интерфейс Border и рисует рамку с помощью кривых Безье и средств Java2D. Прежде всего, отметьте конструктор класса: он позволяет программисту-клиенту задавать толщину кривых (значение может быть дробным, это входит в возможности Java2D) и цвета прорисовки кривых и тени. Если вам не нужна тень, достаточно передать в конструктор два одинаковых цвета. Далее мы указываем (в методе getBorderInsets()), что наша рамка будет занимать примерно по 9 пикселов с каждой стороны компонента, для которого она предназначена (это грубый подсчет, на самом деле занимаемое рамкой место зависит от ее толщины, но если не делать толщину кривых огромной, то приближенный подсчет вполне сгодится). Затем мы методом isBorderOpaque() указываем системе прорисовки, что наша рамка не является непрозрачной, то есть не закрашивает всю занимаемую ею область компонента. Это на самом деле так: мы рисуем кривые Безье и тени, но не закрашиваем всю область рамки, так что сквозь кривые вполне могут просвечивать другие компоненты (те, что находятся в стопке ниже компонента с рамкой BezierBorder).

Основная работа происходит в методе paintBorder(). Именно в нем необходимо прорисовать с помощью переданного объекта Graphics нашу рамку. Заметьте, что мы сразу же создаем копию рисующего объекта Graphics, вызывая его метод create(). Эту технику мы описывали еще в главе 4, и так нужно делать всегда. Все дело в том, что в метод paintBorder() передается тот же самый объект Graphics, который используется для прорисовки собственно компонента, его потомков, рамок этих потомков и т. д. Если мы так или иначе поменяем настройку объекта Graphics и не вернем его параметры в исходное состояние (подобным образом при прорисовке рамки мы меняем цвет и включаем сглаживание графики), то наши новые параметры повлияют на прорисовку остальных компонентов и приведут к отталкивающим результатам. Создав копию объекта Graphics, мы преобразуем его к рисующему объекту Graphics2D библиотеки Java2D (это безопасно, поскольку библиотека Java2D используется во