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

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

Уход за деревьями

475

Мы создаем небольшое окно с рамкой, в центре этого окна разместится дерево JTree (как и всегда, дерево предварительно вставлено в панель прокрутки JScrollPane). Данные дерево черпает из несложной модели, которая создается методом createTreeModel(). Модель состоит из пары узлов (корня и его потомка), к которым присоединены три листа, в листьях и располагаются телефонные номера. Чтобы номера было удобно присоединять к узлам (в цикле), они хранятся в массиве строк с названием phoneDirectory.

Специальный редактор для дерева с телефонными номерами описан во внутреннем классе с названием MaskTreeEditor. Данный класс унаследован от абстрактного базового класса AbstractCellEditor (в нем описана большая часть методов интерфейса CellEditor) и реализует интерфейс TreeCellEditor. Интерфейс TreeCellEditor на самом деле унаследован от интерфейса CellEditor, но методы CellEditor, описанные в базовом классе AbstractCellEditor, «считаются» методами TreeCellEditor, которые нам нужно реализовать, поэтому работы остается сделать не так уж и много. Прежде всего мы настраиваем компонент JFormattedTextField, который и будет применяться для редактирования значений, находящихся в узлах дерева. Это делается в конструкторе класса: в блоке try/catch создается форматирующий объект MaskFormatter, описывающий телефонный номер (подробнее о форматирующих объектах мы узнаем в главе 16), который затем передается в поле JFormattedTextField. Обратите внимание, что конструктору класса MaskFormatter требуется передать ссылку на дерево, с которым он будет работать. Чуть позже мы увидим, зачем это нужно. К созданному текстовому полю тут же присоединяется слушатель ActionListener, который оповещается об окончании редактирования. Как только мы получаем подобное оповещение, то вызываем метод stopCellEditing(), описанный в базовом классе AbstractCellEditor. Реализация базового класса при вызове данного метода сообщает слушателям CellEditorListener о том, что редактирование закончено.

Далее мы реализуем метод getTreeCellEditorComponent(), который должен возвратить компонент, используемый для редактирования. Здесь все просто: мы помещаем в текстовое поле строковое значение узла (подразумевая, что в узле хранится правильный телефонный номер) и возвращаем ссылку на это текстовое поле.

Следующая пара методов относится к интерфейсу CellEditor. Метод getCellEditorValue() возвращает значение, находящееся в данный момент в редакторе. В нем мы передаем текст, находящийся в текстовом поле. Метод isCellEditable() определяет, можно ли проводить редактирование для некоторого события EventObject, переданного ему в качестве параметра. Событие мы не анализируем, а просто получаем выделенный узел дерева (ссылку на дерево, как вы помните, мы сохранили в конструкторе) и разрешаем редактирование только для листьев дерева, так как в нашем случае телефонные номера хранятся только в листьях.

После написания редактора остается присоединить его к дереву. Мы включаем для дерева возможность редактирования, а дальше делаем все то же самое, что и в предыдущем примере: создаем стандартный отображающий объект и стандартный редактор DefaultTreeCellEditor, которому передаем ссылки на дерево, отображающий объект и наш новый редактор.

476

ГЛАВА 15

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

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

Дополнительные события деревьев

Пока мы увидели только одни события у деревьев, а именно события, сообщающие об изменении выделенных узлов, да и те скорее были у модели выделения дерева. Однако есть и еще кое-что. Мы можем узнать о моменте развертывания и свертывания узлов дерева, причем как уже о свершившемся факте, так и о моменте непосредственно перед тем, как узел будет развернут. Для этого служат слушатели TreeExpansionListener и TreeWillExpandListener, соответственно. Их присоединяют непосредственно к дереву JTree. Если первое событие полностью информационное, то второе позволяет нам предотвратить развертывание (или свертывание) определенного узла, если вдруг это понадобится. Рассмотрим простой пример:

//TreeExpansionEvents.java

//События при развертывании узлов дерева import javax.swing.*;

import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeWillExpandListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeModel;

import javax.swing.tree.ExpandVetoException; import java.awt.*;

public class TreeExpansionEvents extends JFrame { public TreeExpansionEvents() {

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

JTree tree = new JTree(createTreeModel()); // добавляем слушателей

TreeListener listener = new TreeListener(); tree.addTreeExpansionListener(listener); tree.addTreeWillExpandListener(listener);

Уход за деревьями

477

add(new JScrollPane(tree)); setSize(400, 300); setVisible(true);

}

//листья дерева храним в массивах private String[] langs = {

"<html><b>Java", "<html><pre>Scala", "Ruby" }; private String[] ides = {

"IDEA", "<html><i>Eclipse", "NetBeans" };

//создание несложной модели дерева

private TreeModel createTreeModel() {

//корень нашего дерева

DefaultMutableTreeNode root = new DefaultMutableTreeNode(

"<html><font color=blue>Создание кода");

//основные ветви

DefaultMutableTreeNode lang =

new DefaultMutableTreeNode("Языки"); DefaultMutableTreeNode ide =

new DefaultMutableTreeNode("Среды"); root.add(lang);

root.add(ide);

// присоединяем листья

for (int i=0; i<langs.length; i++) { lang.add(new DefaultMutableTreeNode(langs[i])); ide.add(new DefaultMutableTreeNode(ides[i]));

}

// создаем стандартную модель return new DefaultTreeModel(root);

}

// слушатель событий о развертывании узлов

class TreeListener implements TreeExpansionListener, TreeWillExpandListener {

public void treeExpanded(TreeExpansionEvent event) { System.out.println("Узел развернут: " + event.getPath());

}

public void treeCollapsed(TreeExpansionEvent event) { System.out.println("Узел свернут: " + event.getPath());

}

public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { }

478

ГЛАВА 15

public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {

if ( event.getPath().getLastPathComponent(). toString().equals("Языки") )

throw new ExpandVetoException(event);

}

}

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

new Runnable() {

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

}

}

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

Методы слушателя TreeExpansionListener вызываются уже «постфактум», так что мы можем использовать этот факт только в качестве полезной информации. В примере мы так и поступаем, просто выводя путь к развернутому или свернутому узлу на консоль. Заметьте, что путь развернутого узла можно получить прямо из события с помощью метода getPath().

Методы же слушателя TreeWillExpandListener (их можно определить по слову «will» в названии) вызываются перед развертыванием узла, и что самое интересное, позволяют возбуждать исключение ExpandVetoException. Если вы создадите подобное исключение, для дерева это будет знаком того, что данный узел нельзя разворачивать, или, как в нашем примере, сворачивать. Чтобы определить, что это тот самый узел, мы проверяем его строковое значение, но это не самый гибкий способ, как правило, проще получить данные узла и каким-то образом их проверить. Запустив пример, вы поймете, что развернуть узел с названиями языков можно, а вот свернуть обратно никак не получится. Интересно, что корень дерева при этом все равно можно свернуть, поэтому если вы хотите удостовериться в том, что на экране виден важный для вас узел, имеет смысл следить не только за ним, но и за его узлами-предками.

Резюме

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

Глава 16. Текстовые компоненты

Одной из самых впечатляющих частей библиотеки Swing является пакет javax.swing. text, обеспечивающий ее инструментами для работы с текстом. Благодаря этому пакету в вашем арсенале появляются несколько мощных текстовых компонентов, позволяющих реализовать в приложении средства ввода и редактирования текста любой сложности, начиная от однострочного текстового поля, часто применяемого для получения от пользователя простой информации, и заканчивая многофункциональным текстовым редактором с разнообразными возможностями. Учитывая, что текстовые компоненты используются в приложениях очень часто, мощь и гибкость текстовых компонентов библиотеки Swing будут как нельзя кстати. И действительно, трудно представить себе приложение, в котором нет текстовых компонентов, предназначенных для ввода информации пользователем. В том или ином виде, но текстовые компоненты, в том числе и встроенные в другие компоненты, присутствуют в приложении всегда.

Пакет javax.swing.text тщательно спланирован, качественно реализован и без сомнения очень велик и довольно сложен. Его объем и сложность его изучения вполне сравнимы с объемом и сложностью изучения самой библиотеки Swing, если иметь в виду все ее компоненты и возможности, а не только относящиеся к обработке текста. Не погрешив против истины, можно сказать, что текстовый пакет представляет собой небольшое «королевство» внутри Swing. Тем не менее, как и всегда в случае с библиотекой Swing, вы можете добиться очень многого, не прикладывая для этого титанических усилий и не вникая во все тонкости реализации текстового пакета Swing. Как мы вскоре увидим, несколькими строками кода можно создать вполне приличные инструменты для редактирования текста, достаточные для широкого класса приложений.

Основные возможности всех текстовых компонентов Swing и их базовая архитектура описаны в абстрактном классе JTextComponent из пакета javax.swing.text. Именно от этого класса унаследованы все текстовые компоненты Swing, будь то простое текстовое поле или многофункциональный редактор. Помимо того что в данном базовом классе задаются общие для всех текстовых компонентов свойства и действия (такие как цвет выделенного текста, цвет самого выделения, курсор, сам текст, механизмы работы с буфером обмена), в нем описывается взаимодействие практически всех составных частей пакета javax.swing.text, которых в нем насчитывается очень и очень много. Постепенно, по мере чтения этой главы, мы будем узнавать самое важное о механизмах текстового пакета Swing, пока же стоит отметить, что в текстовых компонентах Swing, в отличие от остальных компонентов библиотеки, модель, вид и контроллер практически полностью разделены. Как известно, текстовые компоненты довольно сложны и состоят из многих элементов. Оказалось, что раздельные элементы архитектуры MVC подходят для них как нельзя лучше. Модель текстовых компонентов представлена довольно простым интерфейсом Document, который позволяет получать информацию об изменениях в документе и хранящийся в нем текст, а также при необходимости изменять полученный текст. Вид, как нетрудно догадаться, в конечном итоге реализован в UI-представителях текстовых компонентов, но составляется он на основе специальных объектов Element и View, больше отвечающих именно текстовым компонентам. Благодаря этим объектам вы сможете гибко настраивать и расширять внешний вид и структуру текстовых компонентов без вмешательства в сложный процесс их конечной прорисовки. Контроллер частично соединен с видом (особенно это касается событий, не связанных с клавиату-

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