
Портянкин И. Swing
.pdf
Текстовые компоненты |
485 |
однострочных текстовых полей мы сразу указываем количество символов, и нередко это правило ошибочно переносится на класс JTextArea. Задаваемые нами в конструкторе количества строк и символов поля определяют его первоначальный предпочтительный размер в контейнере, но не накладывают каких-либо ограничений на объем вводимого текста, который может быть произвольным. Для первого поля мы изменяем шрифт (здесь все аналогично однострочным полям) и задаем нестандартное значение для табуляции, вызывая метод setTabSize(). Данный метод позволяет указать, какое количество символов будет замещать символ табуляции (вставляемый нажатием клавиши Tab). По умолчанию это значение равно 8, но видно, что его можно изменить. Это может быть полезно в текстах с большим количеством отступов, например в текстах программ или в журнальных записях.
Второе текстовое поле создается с помощью конструктора, принимающего в качестве параметров только количества строк и символов; поначалу текста в таком поле не будет. Для второго поля мы меняем свойства, управляющие процессом переноса текста на новые строки, именно эти свойства наиболее полезны при работе с многострочными полями. По умолчанию текст в поле JTextArea не переносится на новую строку (после запуска программы с примером вы убедитесь в этом, взглянув на первое поле). Изменить данное поведение позволяют использованные нами в примере методы. Метод setLineWrap() включает автоматический перенос текста на новую строку. Длинные слова будут переноситься на следующие строки, так что в таком многострочном поле никогда не потребуется горизонтальная прокрутка. Метод setWrapStyleWord() изменяет стиль переноса длинных слов на новые строки. Если вы передадите в этот метод значение true, то слова, не умещающиеся в строке, будут целиком переноситься на строку новую. По умолчанию значение этого свойства равно false, это означает, что текст переносится, как только ему перестает хватать места в строке, независимо от того, в каком месте слова приходится делать перенос.
В заключение текстовые поля добавляются в панель содержимого окна, которое затем выводится на экран. Еще раз обратите внимание на то, что многострочные поля всегда нужно размещать в панелях прокрутки JScrollPane, иначе ваш интерфейс станет неконтролируемым (поскольку при вводе каждого нового символа сверх максимально-

486 |
ГЛАВА 16 |
го их количества по высоте и ширине размер полей будет увеличиваться). Более того, текстовое поле JTextArea не имеет собственной рамки и без панели прокрутки выглядит довольно бледно. В крайнем случае, если вам не хочется использовать панель прокрутки, вы можете вручную задать для него максимальный размер и рамку, но помните, что в таком случае пользователь не сможет увидеть текст, оказавшийся за пределами определенного вами прямоугольника.
Запустив программу с примером, вы увидите, как функционируют многострочные текстовые поля и как влияют на них сделанные нами изменения в их основных настройках. Обратите внимание на разницу в переносе текста: для переноса текста в первом поле приходится вручную нажимать клавишу Enter, а второе поле, когда вы станете набирать в нем текст (или добавите текст программно, с помощью метода setText()), выполнит перенос автоматически, да еще и по границе слов (хотя в нем также действует нажатие клавиши Enter).
Интересно, что для второго многострочного поля включенный режим переноса фактически отключает прокрутку по горизонтали. На снимке экрана хорошо видно, что при недостатке места полосы прокрутки все равно не меняются. На самом деле, зачем включать прокрутку, если мы сказали, что слова в любом случае будут переноситься на новую строку. Сама же панель прокрутки не станет меньше, чем изначально указанный для текстового поля размер на основании столбцов и строк. Этот факт следует учитывать при расположении компонентов в интерфейсе.
Напоследок сведем изученные нами свойства многострочных полей в табл. 16.2.
Таблица 16.2. Свойства многострочных текстовых полей
Свойства |
Описание |
(и методы get/set) |
|
rows, columns |
Задают размеры многострочного текстового поля, в строках и симво- |
|
лах соответственно. Изменить размер поля можно и прямо во время |
|
работы программы, поле JTextArea при этом автоматически проведет |
|
проверку корректности и перерисовку контейнера |
lineWrap, wrapStyleWord |
Управляют включением переноса текста по строкам и типом этого пере- |
|
носа. Когдапереносстрокотключен, второесвойствонедействует. Если |
|
перенос строк включен и второе свойство равно false, перенос происхо- |
|
дит в том месте, где заканчивается строка, независимо от того, в какой |
|
точке слова это случается. В противном случае перенос происходит по |
|
словам, которые не разбиваются на части, а переходят на новую строку |
|
целиком (если такая возможность есть и слово не слишком длинное) |
font |
Позволяет задать шрифт для многострочного текстового поля. Шрифт |
|
по умолчанию задается текущим менеджером внешнего вида и пове- |
|
дения Swing |
tabSize |
Контролирует размер символа табуляции (вставляется нажатием кла- |
|
виши Tab) в текстовом поле. По умолчанию для символа табуляции ис- |
|
пользуются 8 «обычных» символов, это наиболее часто употребляемое |
|
значение |
lineCount, lineOfOffset, |
Данные методы позволяют получить исчерпывающую информацию |
lineStartOffset, lineEnd- |
о распределении текста многострочного поля по строкам. Первый |
Offset (только методы get) |
метод get дает общее количество строк текста в поле. Второй метод |
|
предоставляет возможность узнать, на какой строке поля находится |
|
символ с данным смещением от начала текста. Последние два мето- |
|
да действуют обратным образом: для заданного номера строки они |
|
позволяют узнать смещение символа, находящегося в начале строки |
|
и в конце строки |

Текстовые компоненты |
487 |
Помимо описанных в таблице свойств и методов многострочное поле JTextArea обладает еще парой весьма полезных методов. Метод append() позволяет присоединить к уже имеющемуся в поле тексту новую часть без удаления прежнего содержимого (в отличие от метода setText()). Метод insert() дает возможность вставить в произвольную область находящегося в поле текста новую строку. Оба этих метода избавляют нас от дополнительной «возни» с методом setText(), при использовании которого пришлось бы вручную манипулировать несколькими строками и преобразовывать их.
Редактор JEditorPane
Редактор JEditorPane представляет собой мощный инструмент, способный отображать на экране текст любого формата. В данный момент библиотекой Swing поддерживается два широко распространенных формата: HTML и RTF (Rich Text Format — расширенный текстовый формат)1, но потенциально редактор JEditorPane может отображать текст любого формата, с любыми элементами и любым оформлением. Такую гибкость редактору обеспечивает фабрика классов EditorKit, в обязанности которой входит создание и настройка всех объектов, необходимых для отображения текста некоторого типа2, в том числе модели документа (объекта Document), фабрики для отображения элементов документа ViewFactory, курсора и списка команд, поддерживаемых данным типом текста. Фабрика EditorKit также отвечает за правильное открытие и сохранение документа поддерживаемого ею формата. Так что возможности JEditorPane ограничены лишь наличием фабрик для различных текстовых форматов. Поддерживаемые стандартно форматы RTF и HTML описываются фабриками RTFEditorKit и HTMLEditorKit соответственно.
Редактор JEditorPane, как правило, требуется именно для отображения текста поддерживаемого им формата, для редактирования текста пользователем с предоставлением последнему всех мыслимых возможностей (изменение стилей, вставка компонентов и значков, и пр.) лучше подходит текстовый компонент JTextPane. Класс JTextPane унаследован от JEditorPane, но гораздо удобнее для редактирования. Работа же с классом JEditorPane обычно происходит следующим образом. Вы методом setContentType() задаете, какой тип документа будет отображать редактор, и указываете местоположение документа, вызвав метод setPage() для перехода по URL-адресу документа (в этом случае считывать текст по указанному адресу будет фабрика EditorKit). Можно также самостоятельным считать текст документа и передать его в метод setText(). Редактор отобразит документ (если он был успешно считан) согласно его типу с помощью соответствующей этому типу фабрики EditorKit.
Для документов, которые поддерживают различного рода ссылки, редактор JEditorPane предоставляет слушателей HyperlinkListener. Эти слушатели оповещаются при активизации пользователем ссылки в документе. Слушателям передается URL-адрес активизированной ссылки, так что они могут заставить редактор немедленно перейти по этому адресу, вызвав все тот же метод setPage(), или использовать полученную информацию по своему усмотрению. Как и когда активизируются ссылки и что считается в документе ссылками, зависит, как нетрудно догадаться, от задействованной редактором фабрики EditorKit. Ссылки поддерживаются стандартной фабрикой HTMLEditorKit, необходимой для отображения HTML-документов, так что вы сможете сразу узнать, когда
1 Создатели Swing честно реализовали все возможности, описанные в спецификациях форматов HTML 3.2 и RTF. К сожалению, время от времени Swing оказывается не в состоянии прочитать тот или иной документ в этих форматах: слишком уж много «недокументированных» дополнений и нестандартных возможностей встречается в документах, создаваемых наиболее популярными редакторами, не говоря уже об обилии Javascript. Если вам важна возможность считывать любые документы, придется многое доделать своими руками.
2 Тип текста задается как строка MIME, например, для HTML — это text/html. Этой строке и сопоставляется подходящая фабрика EditorKit.

488 |
ГЛАВА 16 |
пользователь активизирует ссылку3. Есть только одно маленькое «но»: ссылки активизируются, только когда редактирование текста запрещено (свойство editable равно false).
Попробуем теперь с помощью редактора JEditorPane создать простой браузер для просмотра HTML-документов. С помощью адресной строки или ссылок в текущем документе можно будет переходить с одной страницы на другую :
//JEditorPaneBrowser.java
//Простой браузер на основе редактора JEditorPane import javax.swing.*;
import javax.swing.event.*; import java.awt.*;
import java.awt.event.*;
public class JEditorPaneBrowser extends JFrame { // наш редактор
private JEditorPane editor; // текстовое поле с адресом private JTextField address; public JEditorPaneBrowser() {
super("JEditorPaneBrowser"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем пользовательский интерфейс createGUI();
//выводим окно на экран setSize(500, 400); setVisible(true);
}
// настройка пользовательского интерфейса private void createGUI() {
//панель с адресной строкой
JPanel addressPanel = new JPanel(); addressPanel.setLayout(
new FlowLayout(FlowLayout.LEFT)); addressPanel.setBorder(BorderFactory.
createEmptyBorder(5, 5, 5, 5));
//поле для адреса
address = new JTextField(30); // слушатель окончания ввода
address.addActionListener(new NewAddressAction());
3 Слушатели оповещаются о трех событиях, происходящих со ссылками. Самым популярным событием без сомнения является активизация ссылки, но вы также можете узнать о «входе» в область ссылки (например, когда пользователь наводит на ссылку указатель мыши) и «выходе» из этой области. Используя их, вы сможете оперативно обновлять, к примеру, строку состояния своего приложения, так что пользователь будет видеть, по какому адресу предлагает перейти ссылка.
Текстовые компоненты |
489 |
addressPanel.add(new JLabel("Адрес:")); addressPanel.add(address);
// настраиваем редактор try {
//пути к ресурсам нужно записывать
//полностью, вместе с протоколами
editor = new JEditorPane("http://java.sun.com");
}catch (Exception ex) { JOptionPane.showMessageDialog(
this, "Адрес недоступен");
}
editor.setContentType("text/html");
editor.setEditable(false);
//поддержка ссылок editor.addHyperlinkListener(new HyperlinkL());
//добавляем все в окно
add(addressPanel, "North"); add(new JScrollPane(editor));
}
// слушатель, получающий уведомления о вводе нового адреса class NewAddressAction implements ActionListener {
public void actionPerformed(ActionEvent e) { // переходим по адресу
String newAddress = address.getText(); try {
editor.setPage(newAddress);
}catch (Exception ex) { JOptionPane.showMessageDialog(
JEditorPaneBrowser.this,"Адрес недоступен");
}
}
}
// слушатель, обеспечивающий поддержку ссылок class HyperlinkL implements HyperlinkListener {
public void hyperlinkUpdate(HyperlinkEvent he) { // нужный ли это тип события
if ( he.getEventType() == HyperlinkEvent.EventType.ACTIVATED ) {
// переходим по адресу try {
editor.setPage(he.getURL());

490 |
ГЛАВА 16 |
}catch (Exception ex) { JOptionPane.showMessageDialog(
JEditorPaneBrowser.this,"Адрес недоступен");
}
}
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new JEditorPaneBrowser(); } });
}
}
Наш браузер состоит из текстового поля, в котором пользователь будет набирать адрес для перехода, и редактора JEditorPane, предназначенного для отображения документа по указанному в поле адресу. Для начала необходимо создать нужные компоненты и расположить их в контейнере. Этим занимается метод createGUI(). Первым делом создается панель JPanel, в которой будет располагаться текстовое поле для ввода адреса. В ней мы используем менеджер последовательного расположения FlowLayout с выравниванием по левому краю, а также пустую рамку EmptyBorder, позволяющую визуально отделить содержимое панели от границ окна и улучшить восприятие интерфейса. В панель добавляется надпись, поясняющая функцию текстового поля, и само текстовое поле. Как видно, в нем будет 30 символов. К текстовому полю мы присоединяем слушателя окончания ввода, он реализован во внутреннем классе NewAddressAction (при описании текстовых полей мы отмечали в том числе и то, что в них полезно задействовать слушателей ActionListener.) Когда пользователь заканчивает ввод (нажав специальную клавишу, обычно Enter), вызывается метод слушателя actionPerformed(). В нем мы получаем текст, набранный в поле ввода адреса (это должен быть правильный URL-адрес, например такой: http://www.yahoo.com) и методом setPage() переводим редактор на новую страницу. В случае неудачи будет выведено краткое сообщение.
Теперь о настройке редактора JEditorPane. Для его создания мы выбираем конструктор, который позволяет сразу же задать документ для отображения (очень удобно для браузера: ведь в нем всегда есть домашняя страница). В качестве начальной страницы мы выбираем официальный сайт технологии Java, указывая ее полный адрес. Если вы захотите отобразить локальный файл, то это будет не так-то просто: методу setPage() требуется правильный URL-адрес, поэтому вам придется указать протокол доступа (file:)4 и правильный путь к файлу. Процедура не слишком приятная, но о ней придется вспоминать каждый раз, когда вы будете работать с локальными документами и методом setPage() (на самом деле при работе с локальными документами лучше загрузить текст файла самому и изменять его методом setText(), но будем считать, что наш браузер ориентирован на работу в Web). Далее нужно указать, какие документы должен отображать редактор, так чтобы он выбрал и настроил подходящую фабрику EditorKit. Делается это методом setContentType(), у нас документы имеют тип «text/html». Для поддержки ссылок редактирование должно быть отключено, управляем этим свойство editable. Ну и напоследок мы присоединяем к редактору слушателя HyperlinkListener, который будет получать
4Символы «file:» используются для задания относительных путей, таких как images/picture.gif,
асимволы «file://» — для задания абсолютных.

Текстовые компоненты |
491 |
информацию о событиях, происходящих со ссылками документа. Слушатель реализован во внутреннем классе HyperlinkL.
В метод hyperlinkUpdate() класса HyperlinkL приходит вся информация о происходящих со ссылками событиях. Нас интересует только активизация ссылки пользователем, в самом начале метода мы проверяем, подходит ли нам пришедшее событие. Если событием на самом деле являлась активизация ссылки, то мы получаем адрес активизированной ссылки методом getURL() и переводим редактор на новую страницу методом setPage(), передав ему полученный адрес. В случае ошибки на экран выводится краткое сообщение.
Созданные и настроенные компоненты добавляются в окно: панель с полем для ввода адреса размещается на севере, редактор, предварительно включенный в панель прокрутки, — в центре. Запустив программу с примером, вы сможете оценить, как компонент JEditorPane показывает HTML-документы, и увидеть, на что похожи ваши любимые страницы, отображенные средствами Swing.
Правда, выбирать страницы для просмотра нужно аккуратнее: ни языки сценариев, ни модули расширения, ни возможности HTML 4.0 и выше не поддерживаются, так что не удивляйтесь, если порядочная часть страниц нашему браузеру так и не поддастся. Впрочем, не все так плохо: поддержка HTML позволяет несколькими строками кода выводить правильно составленные документы и, что еще лучше, сохранять в этом формате документы, созданные вашими программами. Браузеры и веб-страницы — это отдельная история, и если уж вам потребуется выводить на экран любые страницы, попробуйте воспользоваться специальными компонентами JavaBeans от сторонних производителей. Еще одним вариантом может быть использование класса Desktop из пакета java.awt, предоставляющего доступ к системному браузеру. Получив активный экземпляр этого класса методом getInstance(), а затем вызвав метод browse(), вы сможете открыть для пользователя окно с системным браузером, используемым по умолчанию. Иногда это именно то, что нужно.
Редактирование по максимуму — компонент JTextPane
Если компонент JEditorPane используется в основном для статичного отображения уже созданных текстовых документов некоторого формата (заданного фабрикой EditorKit), то унаследованный от него текстовый компонент JTextPane незаменим при создании в приложении многофункционального текстового редактора. Обладая всеми впечатляющими возможностями своего предка JEditorPane, класс JTextPane добавляет к нему то, без чего представить себе современные редакторы практически невозможно — разметку текста
492 |
ГЛАВА 16 |
стилями. Для этого в нем используется специальная модель документа StyledDocument и настроенная на поддержку такой модели фабрика классов StyledEditorKit.
Концепция стиля в текстовом редакторе очень проста, тем не менее она многократно повышает эффективность работы пользователя с текстом. Стиль (style) — это некоторый набор атрибутов редактируемого текста (такими атрибутами могут быть шрифт текста, его размер и цвет, выравнивание и т. п.), который можно применить к любому фрагменту текста. Стиль позволяет четко разделить внешний вид документа и собственно текст. Пользователь может сосредоточиться на наборе текста, используя при этом несколько стилей (пара заголовков, основной текст, текст сносок), а если у него возникнет необходимость оформить текст по-другому, понадобится лишь поменять атрибуты стилей. Текст, набранный с использованием стилей, изменится автоматически. Вы найдете стили в любом современном редакторе, в том числе они встроены в язык разметки HTML посредством расширений CSS (Cascaded Style Sheets — каскадные таблицы стилей).
Помимо стилей (в компоненте JTextPane стили идентифицируются строковыми именами) поддерживаются и просто неупорядоченные наборы атрибутов текста, заданные объектами AttributeSet. Наборы атрибутов позволяют изменять произвольные фрагменты текста или даже целые абзацы, слегка отступая от комплекта заранее заданных стилей. Это также очень полезная возможность, хотя всегда лучше создавать свой уникальный стиль на каждый отдельный формат текста.
Одной из самых полезных является способность стилей образовывать иерархии. Одни стили могут наследовать атрибуты других стилей, прибавляя при этом что-то свое. К примеру, стиль для основного текста может определять шрифт, его размер и выравнивание, а стиль для заголовка, унаследованный от этого базового стиля, может поменять только размер шрифта, не меняя остальное. Иерархия стилей дает пользователю возможность еще быстрее настраивать внешний вид и формат документа, не затрагивая текста: сменив шрифт в основном стиле, пользователь сменит его и для всех унаследованных от него стилей.
Рассмотрим пример, в котором применим часть впечатляющих возможностей компонента JTextPane, в том числе наборы атрибутов и именованные стили. После разбора примера мы сможем в деталях понять, как работают механизмы класса
JTextPane:
//StyledText.java
//Богатые возможности редактора JTextPane import javax.swing.*;
import javax.swing.text.*; import java.awt.*;
public class StyledText extends JFrame { public StyledText() {
super("StyledText"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создадим редактор
JTextPane textPane = new JTextPane();
//создание документа и стилей createDocument(textPane);
//добавим редактор в окно add(new JScrollPane(textPane));
//выводим окно на экран
Текстовые компоненты |
493 |
setSize(400, 300); setVisible(true);
}
private void createDocument(JTextPane tp) {
//настройка стилей
//стиль основного текста
Style normal = tp.addStyle("Normal", null); StyleConstants.setFontFamily(normal, "Verdana"); StyleConstants.setFontSize(normal, 13);
// заголовок
Style heading = tp.addStyle("Heading", normal); StyleConstants.setFontSize(heading, 20); StyleConstants.setBold(heading, true);
//наполняем документ содержимым, используя стили insertString("Незамысловатый Заголовок", tp, heading); insertString("Далее идет обычное содержимое,", tp, normal); insertString("помеченное стилем Normal.", tp, normal); insertString("Еще Один Заголовок", tp, heading);
//меняем произольную часть текста
SimpleAttributeSet red = new SimpleAttributeSet(); StyleConstants.setForeground(red, Color.red); StyledDocument doc = tp.getStyledDocument(); doc.setCharacterAttributes(5, 5, red, false);
// добавим компонент в конец текста tp.setCaretPosition(doc.getLength());
JCheckBox check = new JCheckBox("Все возможно!"); check.setOpaque(false); tp.insertComponent(check);
}
//вставляет строку в конец документа с переносом,
//используя заданный стиль оформления
private void insertString(String s, JTextPane tp, Style style) { try {
Document doc = tp.getDocument(); doc.insertString(doc.getLength(), s + "\n", style);
}catch (BadLocationException ex) { ex.printStackTrace();
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(

494 |
ГЛАВА 16 |
new Runnable() {
public void run() { new StyledText(); } });
}
}
В примере мы создаем текстовый редактор JTextPane, который разместится в центре нашего окна с рамкой (редактор предварительно размещен в панели прокрутки JScrollPane). Самые захватывающие события происходят в методе createDocument(), в котором мы создаем стили и наполняем документ содержимым.
Сначала создаются два именованных стиля, которые мы затем будем использовать при разметке своего текста. Посмотрите, как это делается: сначала создается базовый для всего документа стиль (с названием Normal). Для добавления стиля в редактор применяется метод с говорящим названием addStyle()5. Ему нужно передать два параметра: название нового стиля и стиль, который будет являться родительским для нового стиля, а в ответ он вернет новый стиль Style. Если в качестве второго параметра передать пустую ссылку null, стиль окажется без родителя и будет создаваться «с нуля». Именно таким образом мы создаем базовый стиль своего документа
. Второй стиль в нашем документе (у него название Heading) служит для заголовков. Он унаследован от стиля Normal, а значит, перенимает от своего родителя все установленные для того атрибуты, такие как размер и цвет шрифта. Стиль заголовка отличается увеличенным размером шрифта и полужирным начертанием. Обратите внимание, что для установки атрибутов стилей используются удобные статические методы класса StyleConstants.
После создания стилей можно приступать к вставке в документ текста, размеченного только что настроенными стилями. Вставить текст в компонент JTextPane можно только посредством модели документа Document, в которой имеется метод insertString(). Этот метод требует трех параметров: позицию для вставки, строку, которую вы собираетесь вставлять, и стиль, который будет иметь вставляемая строка. У нас вставкой текста в документ занимается собственный вспомогательный метод insertString(). В нем строка добавляется к концу документа и при этом дополняется символами переноса строки (их приходится указывать вручную, автоматически они не добавляются). Для вставки текста в конец документа в качестве первого параметра метода insertString() необходимо указать размер документа, который несложно узнать с помощью метода getLength(). Кстати, приходится следить за исключением, которое не преминет возникнуть в том случае, если позиция в документе будет неверно указана6. С помощью вспомогательного метода мы добавляем в документ текст: сначала заголовок, потом несколько строк с обычным стилем и снова заголовок. При этом используются созданные нами стили.
Далее демонстрируется, как можно изменять оформление произвольного фрагмента текста, каким бы стилем он ни был размечен. Для этого предназначен метод setCharacterAttributes(), которому необходимо указать диапазон в тексте, набор атрибутов, а также булево значение. Последнее указывает, нужно ли полностью заменить имеющийся стиль новым набором атрибутов, или надо совместить имеющийся стиль с новым
5 Такой же метод имеется и в модели StyledDocument. На самом деле практически все методы, определенные в классе JTextPane и так или иначе манипулирующие текстом, его атрибутами и стилями, определены в модели документа StyledDocument. В больших приложениях лучше работать с моделью напрямую, мы прекрасно знаем, сколько преимуществ это несет.
6 Случай с текстовыми компонентами и работой с их текстом как раз показывает, как иногда бывают надоедливы проверяемые (checked) исключения. Как правило, манипулируя текстом, мы осознаем его границы, и здесь вполне хватило бы непроверяемого исключения, которое не обязательно включать в блок try.