Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

510

ГЛАВА 16

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

Команды отмены и повторения операций очень просты, во многом благодаря услугам объекта UndoManager. Они проверяют, возможны ли сейчас отмена или повторение операций, и если да, выполняют отмену (методом undo()) или повторение (redo()). Управлять списком операций UndoableEdit будет класс UndoManager. Запустив программу с примером и работая с текстом, вы увидите, каким образом кнопки панели инструментов позволяют отменять или повторять операции.

Класс UndoManager обладает несколькими дополнительными интересными возможностями и зачастую его хватает для достаточно гибкого управления операциями, происходящими в ваших текстовых компонентах. Если же возникнет необходимость в особом поведении, вы всегда сможете самостоятельно реализовать интерфейс слушателя UndoableEditListener, присоединить его к модели документа и распоряжаться информацией об операциях так, как вам потребуется. Увы, но по умолчанию никакой отмены или повторения операций в текстовых компонентах Swing не поддерживается, так что вам всегда придется добавлять ее самостоятельно — в противном случае работать с вашими текстовыми компонентами будет не слишком удобно. Для поддержки отмены и повтора операций через клавиатуру чрезвычайно хорошо подходят карты клавиатурных сокращений InputMap, которые вы можете настроить на вызов тех же самых команд, что настроены на панели инструментов, в случае нажатия подходящего сокращения (обычно

Control-Z).

Управление курсором интерфейс Caret

Важную роль при работе с текстом играет текстовый курсор (caret), который показывает пользователю, в каком месте будут появляться набираемые им символы, позволяет перемещаться по строкам, столбцам и другим элементам текста, а также управляет процессом выделения текста. Курсор текстовых компонентов библиотеки Swing (а к таким компонентам относятся те, что унаследованы от базового класса JTextComponent), должен реализовывать интерфейс Caret из пакета javax.swing.text. Все знакомые нам текстовые компоненты используют стандартную реализацию этого интерфейса DefaultCaret.

Именно курсор позволяет программно изменять текущую позицию в тексте (это

втом числе означает и прокрутку текста до нужной позиции, если она в данный момент не видна на экране) и управлять выделенным текстом (узнавать, что в данный момент выделено или выделять текст программно). Правда, для работы с выделенным текстом

вбазовом классе JTextComponent имеется несколько удобных методов, но изначально вы-

деление текста находится в ведении текстового курсора.

Два основных свойства курсора — его позиция (вы сможете управлять ею посредством свойства dot) и начало отсчета (узнать его позволяет метод getMark()). Позиция курсора показывает, где будут появляться набираемые пользователем или вставляемые вами программно символы. Начало отсчета служит для поддержки выделения текста или позиции курсора, когда выделенного текста нет. Если же выделение текста происходит, начало отсчета указывает на начало выделенного фрагмента, а позиция указывает на конец этого фрагмента (в конце выделенного фрагмента находится курсор). В зависимости от направления выделения (вперед по тексту или назад) начало отсчета может быть больше позиции или меньше ее.

Курсор, описанный интерфейсом Caret, поддерживает событие ChangeEvent, оно запускается каждый раз при изменении позиции курсора. Присоединив к курсору слушателя ChangeListener, вы сможете получать подробную информацию обо всех

Текстовые компоненты

511

перемещениях курсора в вашем текстовом компоненте. Кроме того, базовый класс JTextComponent поддерживает событие CaretEvent. Оно также запускается при перемещениях курсора, но если событие ChangeEvent содержит только источник события (сам курсор), то событие CaretEvent позволяет сразу же узнать текущую позицию курсора и начало отсчета.

Рассмотрим теперь небольшой пример и увидим, как можно управлять курсором, используемым в текстовых компонентах Swing по умолчанию, а также узнавать от него

особытиях:

//ControllingCaret.java

//Управление текстовым курсором import javax.swing.*;

import javax.swing.event.CaretListener; import javax.swing.event.CaretEvent; import javax.swing.text.*;

import java.awt.*;

public class ControllingCaret extends JFrame { public ControllingCaret() {

super("ControllingCaret"); setDefaultCloseOperation(EXIT_ON_CLOSE); // многострочное текстовое поле

final JTextArea textArea = new JTextArea(); // добавим текст

textArea.append("Просто какой-то текст");

//настройка курсора и выделение текста

Caret caret = textArea.getCaret(); caret.setBlinkRate(50); caret.setDot(5); caret.moveDot(10);

//добавим текстовое поле в окно add(new JScrollPane(textArea));

//надпись для слова у курсора

final JLabel label = new JLabel(); add(label, "South");

// слушатель перемещений курсора textArea.addCaretListener(new CaretListener() {

public void caretUpdate(CaretEvent e) { // выведем слово у курсора

try {

// начало и конец слова на позиции int wordStart =

Utilities.getWordStart(textArea, e.getDot()); int wordEnd =

512

ГЛАВА 16

Utilities.getWordEnd(textArea, e.getDot()); label.setText(textArea.getText(

wordStart, wordEnd — wordStart));

}catch (BadLocationException ex) { ex.printStackTrace();

}

}

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

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

}

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

new Runnable() {

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

}

}

Мы создаем небольшое окно, в центре его разместится многострочное текстовое поле JTextArea, «обернутое» в панель прокрутки. Для того чтобы можно было работать с курсором, необходим какой-либо текст, так что мы добавляем в поле строку текста, вызывая метод append(). Далее мы получаем используемый в текстовом поле курсор (для этого служит метод getCaret()) и настраиваем для него некоторые свойства.

Прежде всего меняется частота мерцания курсора, задаваемая свойством blinkRate. Это промежуток между мерцаниями в миллисекундах. В нашем случае курсор будет мерцать довольно быстро: мы задаем промежуток равным всего 50 миллисекундам. Мерцание можно и отключить: достаточно задать нулевой промежуток. Далее мы устанавливаем позицию курсора равной 5 и выделяем фрагмент текста. Текст удобно выделять с помощью метода moveDot(): он перемещает позицию в то место, что вы ему указываете, а начало отсчета делает равным прежнему значению позиции. В примере мы выделяем фрагмент текста в пять символов с пятой позиции по десятую.

На юге окна мы размещаем надпись, в которой будем писать слово, находящееся на позиции курсора. Чтобы следить за курсором, мы присоединяем слушателя для событий CaretEvent прямо к текстовому полю, и при наступлении события, пользуясь уже знакомыми нам методами класса Utilities, устанавливаем текущее слово в качестве текста для надписи. Как видно, событие CaretEvent позволяет узнать текущую позицию курсора в тексте с помощью метода getDot().

Текстовые компоненты

513

Запустив программу с примером, вы увидите, каким образом проведенные нами манипуляции отразились на текстовом поле и каким образом мы мгновенно узнаем о слове на текущей позиции курсора. Курсор Caret незаменим при управлении текущей позицией в тексте и работе с выделением, он позволяет мгновенно узнавать о перемещениях в тексте, так что при работе с текстом вы не раз будете обращаться к его возможностям.

Дополнительное выделение текста

При работе с текстом, имеющим определенную структуру, таким например, как язык программирования или язык разметки наподобие HTML, возможность дополнительно размечать часть текста приходится весьма кстати. Это может быть, например прорисовка тегов в языке разметки другим цветом, выделение находящейся в данный момент у курсора единицы языка по всему тексту программы, или другие задачи, требующие помечать некий текст.

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

Именно рисование фона применяется для прорисовки выделенного текста, однако это решение доступно и нам, и не ограничивается выделением. В архитектуру текстовых компонентов встроен рисующий объект, описанный интерфейсом Highlighter, который прорисовывает фон текста, перед тем как будет прорисован сам текст. Вы можете реализовать его самостоятельно и задать для любого текстового компонента, используя свойство с одноименным названием. Однако, как это бывает в Swing, почти всегда есть возможность встать на плечи гигантов и воспользоваться стандартной реализацией. Дело в том, что интерфейс Highlighter предусматривает добавление новых регионов для прорисовки фона и их последующее удаление. Прорисовкой же самого фона для заданного региона текста занимается отдельный объект HighlightPainter. Таким образом, вы можете взять стандартный объект, рисующий фон, из любого текстового компонента и добавить к нему дополнительный регион текста, а прорисовку реализовать в интерфейсе HighlightPainter, или же и здесь использовать стандартную реализацию DefaultHighlightPainter, которая рисует фон в одном цвете.

Чтобы не быть голословными, рассмотрим простой пример и убедимся, насколько просто размечать фон для текста с помощью стандартных средств Swing.

//TextHighlights.java

//Дополнительное выделение текста import javax.swing.*;

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

public class TextHighlights extends JFrame { public TextHighlights() {

super("TextHighlights"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем и настраиваем поле

JTextArea area =

new JTextArea("Привет мир!");

514 ГЛАВА 16

try {

// добавим выделенный фрагмент желтого цвета

Object reference = area.getHighlighter().

addHighlight(0, 6,

new DefaultHighlighter. DefaultHighlightPainter(Color.YELLOW));

} catch (BadLocationException e) { e.printStackTrace();

}

//добавляем поле в окно add(new JScrollPane(area));

//выводим окно на экран setSize(300, 200); setVisible(true);

}

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

new Runnable() {

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

}

}

В примере мы создаем многострочное текстовое поле и размещаем его в центре нашего окна. Далее мы просто получаем используемый в нем по умолчанию рисующий объект фона, и методом addHighlight() добавляем новую выделенную область. Обратите внимание на параметры метода — это начало и конец фрагмента текста, фон которого вы собираетесь рисовать, а также рисующий объект. Мы использовали стандартный объект, попросив его рисовать фон желтым цветом. Метод же возвращает ссылку на добавленный фрагмент прорисовки фона. В дальнейшем эту ссылку можно использовать, чтобы поменять фрагмент или совсем удалить его методами changeHighlight() и removeHighlight(). Запустив программу, вы увидите, как работает прорисовка фона текста.

Пример намеренно сделан очень простым, чтобы продемонстрировать непосредственно прорисовку фона, однако в качестве упражнения вы можете совместить его с предыдущим примером, а именно следить за курсором, получать слово на текущей позиции и выделять такое слово с помощью фонового рисования по всему доступному тексту. Таким образом вы создадите простой прототип мгновенного поиска слова по тексту и примените все возможности фонового рисования, в том числе сможете и удалять уже добавленные фрагменты фона.

Источник: https://studfile.net/preview/15935977/