Текстовые компоненты |
505 |
чуть позже, это необходимо нам для выделения текста. Заодно он сразу же присоединяет модель к этому компоненту методом setDocument(). Новые методы позволяют контролировать автоматическое заполнение: метод addWord() добавляет в список слов новое слово; именно список слов words содержит слова, которые могут быть дополнены автоматически, а свойство beforeCompletion (и метод set) определяет, какое количество букв в слове должен набрать пользователь, прежде чем слово будет анализироваться на предмет автоматического дополнения. По умолчанию свойство beforeCompletion равно трем.
Для автоматического заполнения потребуется метод, который вызывается при вставке в документ нового содержимого (это метод insertString()); именно при вставке (наборе) нового текста пользователь может ожидать подсказки от текстового поля. Удаление содержимого, как правило, не должно сопровождаться автоматическим заполнением, это может привести к путанице и излишне усложнить работу с полем. Замена же текста сводится к поэтапному удалению фрагмента текста и вставке нового текста на место старого. Она вполне подходит нашему алгоритму.
Первым делом в методе insertString() мы получаем необходимую для дальнейшей работы текущую позицию курсора в документе end (получить ее можно, суммируя смещение вставки в документе, переданное в метод как параметр offs, и длину нового текста для вставки). Далее нам необходимо определить, какое слово находится в данный момент на текущей позиции курсора. Здесь нам неоценимую услугу окажут статические методы класса Utilities8. Метод getWordStart() позволяет получить позицию начала слова. В качестве параметров им требуется передать текстовый компонент, в котором находится анализируемый текст, и позицию, с которой надо начинать поиск слова. В нашем случае позиция известна: это позиция начала вставки, переданная нам параметром. После получения позиции текущего слова мы проверяем, можно ли начинать его дополнение, то есть достаточна ли длина этого слова (она должна быть не меньше свойства beforeCompletion). Если слово недостаточно длинное, текст вставляется без изменений.
В случае если слово имеют достаточную длину, мы начинаем его поиск в списке слов, которые можно дополнять. Для этого мы перемещаемся по списку со словами words, проверяя, не начинается ли некое слово с набранных пользователем символов. Если такое слово обнаруживается в списке, оно сохраняется в переменной wholeWord, после чего поиск заканчивается. Если слово не обнаруживается, работа заканчивается. После обнаружения подходящего слова остается получить ту его часть, которую необходимо предложить пользователю в качестве подсказки, вставить эту часть в документ, а затем выделить ее.
В нашем примере вставка текста в документ очень проста, поскольку мы и являемся документов — мы просто добавляем часть для дополнения к новому тексту и передаем его родительскому методу, который и будет хранить его. Выделение добавленной части осуществляется из потока рассылки событий EventDispatchThread, с помощью метода invokeLater() класса EventQueue. Выделение позволит пользователю быстро отказаться от нашей подсказки, если она ему не подойдет, и продолжить печатать свой вариант как ни в чем не бывало. Текст необходимо выделять из потока рассылки событий, потому что, во-первых, мы будем проводить манипуляции с графическим компонентом Swing, а вовторых, нам нужно «подождать», пока новый текст окажется в поле. Это сделает наш предок, когда мы вызовем его родительский метод и выполнение метода insertString() закончится, а затем уже, когда дойдет очередь в потоке событий, вызовется наш метод run(), который выделит новый текст.
8 Это класс с набором статических методов, он находится в пакете javax.swing.text, и содержит несколько весьма полезных алгоритмов для работы с текстовыми компонентами и их моделями. Если вы собираетесь вплотную заниматься текстовыми возможностями Swing, обратите на данный класс свое внимание. Методы для нахождения слов в тексте к тому же полностью готовы к работе со всеми возможными языками, а не основаны на примитивных алгоритмах.
506 |
ГЛАВА 16 |
Теперь, когда мы создали нашу модель документа, необходимо проверить ее в действии. Следующий простой пример показывает, на что она способна:
//TestAutoCompletion.java
//Проверка работы текстового поля с
//автоматическим заполнением
import com.porty.swing.*; import javax.swing.*; import java.awt.*;
public class TestAutoCompletion extends JFrame { public TestAutoCompletion() {
super("TestAutoCompletion"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем и настраиваем поле
JTextField field = new JTextField();
field.setColumns(15);
//слова для автозаполнения
AutoCompleteTextDocument doc =
new AutoCompleteTextDocument(field); doc.addWord("прекрасный"); doc.addWord("великолепный");
//добавляем поле в окно
setLayout(new FlowLayout()); add(field);
// выводим окно на экран setSize(300, 200); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new TestAutoCompletion(); } });
}
}
В примере создается небольшое окно с рамкой, в него мы добавляем текстовое поле. После создания текстового поля необходимо создать нашу модель с автоматическим заполнением и передать поле в ее конструктор (в нем модель добавится к полю автоматически), а также провести небольшую настройку: количество столбцов следует задать равным 15 и в список слов для автоматического заполнения добавить два подходящих прилагательных. Запустив программу с примером и набирая добавленные в список слова, вы увидите, как поле услужливо предоставляет вам подсказки при вводе.
Текстовые компоненты |
507 |
Эти подсказки, если они отображают то, что вы хотели набрать, значительно ускоряют ваш труд и легко устраняются, если вы пожелаете набрать что-либо иное. Работать с такими компонентами удобно, и они добавляют вашему пользовательскому интерфейсу немало блеска.
Вы можете задействовать созданное нами текстовое поле не только в качестве отдельного элемента интерфейса, но и в качестве редактора для сложных компонентов Swing, таких как списки или таблицы. Так вы сделаете и эти компоненты значительно более удобными, особенно если правильно подберете список слов для автоматического заполнения.
Рассмотренный нами в этом параграфе подход, когда мы берем стандартную модель документапростыхтекстовыхполейPlainDocument ипереопределяемееметодinsertString(), очень полезен, и его определенно стоит взять на заметку. Если с простыми случаями ввода специальных данных может справиться поле с форматированием JFormattedTextField, то в более сложных ситуациях очень ценится возможность полностью контролировать процесс ввода данных и незамедлительно их менять , что мы и увидели. Еще одним вариантом является применение слушателя документа DocumentListener, но в этом случае модифицировать текст гораздо труднее, поскольку слушателю достается информация уже после того, как изменения произошли, и любые изменения текста в нем опять же приводят к вызову методов слушателя, а это не совсем элегантно.
Отмена и повтор операций
К модели документа Document можно присоединить не только слушателя DocumentListener. Есть событие и соответствующий ему слушатель еще одного типа — это событие UndoableEditEvent и слушатель UndoableEditListener. Данное событие модель документа обеспечивает путем поддержки операций повтора и отмены (undo/redo). Эти операции можно встретить во всех современных текстовых компонентах (и не только в текстовых), и они весьма удобны: пользователь может не опасаться совершить какуюлибо ошибку, к примеру, случайно удалить набранный текст. К его услугам всегда имеется возможность отмены операций, позволяющая привести документ к прежнему виду. Повтор операции аналогичным образом дает пользователю возможность снова выполнять ту операцию, от которой он по ошибке отказался. Все текстовые компоненты в ваших программах должны поддерживать по меньшей мере отмену операций, иначе ваш интерфейс рискует стать очень неудобным.
Если уже знакомое нам событие DocumentEvent несет в себе подробную информацию об изменениях в структуре и содержимом документа, то у события UndoableEditEvent совершенно иная цель. Оно должно сообщать, что пользователь совершил в документе какую-то операцию, которую можно отменить или повторить. Такая операция описана в классе UndoableEdit, объект именно этого класса возвращает метод getEdit() события UndoableEditEvent. В данном объекте описано немного: имя операции, если оно есть (у некоторых операций могут быть различные имена для отмены и повтора), возможность отмены или повторения операции, и два основных метода, которые и занимаются отменой и повторением. Больше никакой информации объект UndoableEdit в себе не таит, да
508 |
ГЛАВА 16 |
это ему и не нужно: единственная его цель состоит в описании операции, совершенной в документе. Подробности сообщает событие DocumentEvent.
Теперь вы можете наделять свои текстовые компоненты возможностью отмены и повторения операций: достаточно зарегистрировать в модели документа слушателя UndoableEditListener, получить уведомления о произведенных пользователем операциях, хранить историю операций (например, в виде списка объектов UndoableEdit) и при запросе пользователя вызывать для последних метод undo() или redo(). Правда, есть еще один путь: для удобства программистов библиотека Swing предоставляет небольшой пакет javax.swing.undo. Ядром данного пакета является класс UndoManager, вы можете регистрировать его в любом текстовом компоненте как слушателя UndoableEditListener. Этот класс позаботится о хранении истории операций и предоставит удобный программный интерфейс, позволяющий быстро наладить отмену и повторение операций в вашем приложении.
Рассмотрим пример, в котором мы обеспечим многострочное текстовое поле JTextArea специальной панелью инструментов, позволяющей отменять и повторять произведенные в поле операции. Для удобства работы хранить операции и работать с ними мы будем посредством услуг класса UndoManager. Вот что получается:
//UndoAndRedo.java
//Поддержка отмены и повтора операций
//в текстовых компонентах Swing import javax.swing.*;
import javax.swing.undo.*; import java.awt.event.*; import java.awt.*;
public class UndoAndRedo extends JFrame { // поддержка отмены/повтора операций
private UndoManager undoManager = new UndoManager(); public UndoAndRedo() {
super("UndoAndRedo"); setDefaultCloseOperation(EXIT_ON_CLOSE); // панель инструментов
JToolBar toolBar = new JToolBar(); toolBar.add(new UndoAction()); toolBar.add(new RedoAction());
// текстовое поле
JTextArea textArea = new JTextArea();
//добавляем слушателя операций textArea.getDocument().
addUndoableEditListener(undoManager);
//добавляем компоненты в окно add(toolBar, "North");
add(new JScrollPane(textArea));
//выводим окно на экран
setSize(400, 300);
Текстовые компоненты |
509 |
setVisible(true);
}
// команда — отмена операции
class UndoAction extends AbstractAction { public UndoAction() {
// настройка команды putValue(AbstractAction.SMALL_ICON,
new ImageIcon("undo16.gif"));
}
public void actionPerformed(ActionEvent e) { if ( undoManager.canUndo() )
undoManager.undo();
}
}
// команда — повтор операции
class RedoAction extends AbstractAction { public RedoAction() {
// настройка команды putValue(AbstractAction.SMALL_ICON,
new ImageIcon("redo16.gif"));
}
public void actionPerformed(ActionEvent e) { if ( undoManager.canRedo() )
undoManager.redo();
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new UndoAndRedo(); } });
}
}
В центре нашего окна размещается многострочное текстовое поле JTextArea, предварительно «обернутое» в панель прокрутки JScrollPane. На севере окна мы размещаем панель инструментов JToolBar; она состоит из двух кнопок, отвечающих за отмену и повторение операций. Кнопки создаются на основе команд Action, описанных в виде внутренних классов. Настройка команд выполняется в конструкторе: мы меняем свойство команды SMALL_ICON, задавая для кнопок значок9. Само же действие, выполняемое кнопкой, происходит в методе actionPerformed().
Для хранения происходящих в текстовом поле операций и их последующей отмены или повторения мы воспользуемся услугами класса UndoManager. Для того чтобы он
9 Значки для кнопок из этого примера были взяты из коллекции Java Look and Feel Graphics Repository. Вы можете найти ее на сайте java.sun.com.