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

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

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

465

}

}

}

}

Мы наследуем наше дерево от класса JTree и определяем только один конструктор на основе модели TreeModel. Как мы знаем, остальные конструкторы, создающие дерево с помощью простых структур данных, не слишком полезны, так что от их отсутствия мы потеряем немного. В конструкторе сразу же происходит дополнительная настройка дерева: мы задаем для него особый отображающий объект, который будет ответственен за вывод на экран флажков, а также присоединяем к нему слушателя событий от мыши MouseListener. Слушатель событий будет следить за нажатиями кнопок мыши и менять состояние флажков: мы уже знаем, что компоненты, служащие для отображения узлов дерева, не получают никаких событий (так как фактически отсутствуют на экране), поэтому их приходится поддерживать самостоятельно.

Отображающий объект для нового дерева унаследован от флажка JCheckBox (так будет проще настраивать свойства флажка) и реализует необходимый интерфейс

TreeCellRenderer. Метод getTreeCellRendererComponent(), определенный в данном интерфейсе, должен вернуть для некоторого узла дерева (известны само дерево, отображаемый узел и его состояние, в том числе, выбран ли он, является ли листом и т. п. — все это передается в качестве параметров) компонент, который данный узел отобразит. Так же как и в случае со списками и их отображающими объектами, компонент, как правило, один, он настраивается для каждого отображаемого элемента. Обратите внимание, что в конструкторе мы меняем свойство непрозрачности, устанавливая его в false. Флажки JCheckBox по умолчанию непрозрачны, а в составе других компонентов непрозрачные компоненты выглядят не слишком привлекательно, закрашивая все вокруг себя цветом фона. Для начала в методе getTreeCellRendererComponent() мы проверяем, что деревом используется стандартная модель и узлы представлены классом DefaultMutableTreeNode. Если это не так, работа передается стандартному отображающему объекту DefaultTreeCellRenderer, который специально для подобных случаев хранится в нашем дереве в виде переменной. Благодаря стандартному объекту наше новое дерево будет способно отображать не только флажки, но и любые другие элементы.

Если в дереве используется стандартная модель, то мы преобразуем узел к соответствующему типу и получаем значение, которое хранится в узле. Если значением является объектом CheckBoxListElement, в котором хранятся название узла и признак его выделения, в действие вступает наш отображающий объект. Он настраивает свойства флажка (флажком является сам отображающий объект), а именно текст и выделение, и возвращает флажок в качестве объекта, отображающего данный узел. Когда в узле хранится не объект CheckBoxListElement, а что-то иное, в действие снова вступает стандартный отображающий объект, который способен отобразить любой объект в виде строки. Обратите внимание, что мы не поддерживаем выделение цветом или рамку, когда флажок обладает фокусом или выбран. В этом примере мы будем считать, что главное — отобразить само состояние флажка, ну а добавить все вышеперечисленное очень просто, в том случае, если эти свойства вам понадобятся, надо будет лишь задать нужный цвет и рамку для флажка5.

5 Интересно, что найти цвет для выделенного узла дерева не так-то просто — в дереве JTree нет метода для его определения. Вы можете задействовать для этой цели стандартный отображающий объект, получив от него компонент с нужными настройками и получив его цвет фона методом getBackground().

466

ГЛАВА 15

Отображающий объект отвечает за вывод на экран флажков, слушатель мыши, присоединенный к дереву, обеспечит смену состояния флажков. Компонент, используемый для отображения узлов, в контейнер не добавляется, так что все необходимые ему события необходимо поддерживать вручную. В слушателе мы следим за нажатиями кнопок мыши. Прежде всего необходимо выяснить, в районе какого узла произошло нажатие кнопки. В этом нам помогает метод getClosestPathForLocation(), он возвращает путь TreePath до нужного нам узла. Данный метод может возвращать и пустую (null) ссылку (так может случиться, если в дереве не будет данных), так что нам необходимо проверить это. Если проверка успешна, мы получаем последний элемент пути — как нам уже известно, это тот узел, в районе которого и произошло событие. Подразумевая, что дерево использует стандартную модель, мы преобразуем узел к объекту DefaultMutableTreeNode и получаем хранящиеся в этом объекте данные (проверив предварительно тип узла). Если это данные нужного нам типа (CheckBoxListElement), то состояние флажка меняется на противоположное, после чего дерево перерисовывается. Обратите внимание на метод перерисовки: мы перерисовываем лишь нужный нам узел, а получить его прямоугольник на экране помогает метод дерева под названием getPathBounds(). В небольших деревьях «точечная» перерисовка вряд ли будет заметна, но в больших «лесах» с тысячами листьев вы обязательно оцените производительность.

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

//TestCheckBoxTree.java

//Проверка дерева с поддержкой флажков import com.porty.swing.*;

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

public class TestCheckBoxTree extends JFrame { public TestCheckBoxTree() {

super("TestCheckBoxTree"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создаем модель и дерево

TreeModel model = createTreeModel(); CheckBoxTree tree = new CheckBoxTree(model);

//добавляем дерево в окно

add(new JScrollPane(tree)); // выводим окно на экран setSize(400, 300); setVisible(true);

}

// листья дерева храним в массивах

private String[] langs = { "Java", "Scala", "Ruby" }; private String[] ides =

{ "IDEA", "Eclipse", "NetBeans" };

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

467

// создание несложной модели дерева private TreeModel createTreeModel() {

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

DefaultMutableTreeNode root =

new DefaultMutableTreeNode("Создание кода");

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

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(

new CheckBoxListElement(false, langs[i]))); ide.add(new DefaultMutableTreeNode(

new CheckBoxListElement(false, ides[i])));

}

//создаем стандартную модель

return new DefaultTreeModel(root);

}

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

new Runnable() {

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

}

}

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

DefaultTreeModel.

Остальная часть программы примера и вовсе проста: мы создаем небольшое окно с рамкой, в центр его добавляется новое дерево CheckBoxTree, предварительно вставленное в панель прокрутки JScrollPane. Дерево создается на основе подготовленной модели. После создания модели и дерева остается лишь вывести окно на экран.

468

ГЛАВА 15

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

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

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

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

Редактирование узлов

Узлы, а точнее хранящиеся в них данные, можно не только отображать, но и редактировать. По умолчанию редактирование узлов дерева отключено, но вы всегда можете включить его, присвоив свойству editable значение true. Редактированием узлов, так же как и их отображением, дерево занимается не само, оно поручает эту работу специальному редактору. Для того чтобы некий объект смог стать редактором узлов дерева JTree, ему необходимо реализовать интерфейс TreeCellEditor.

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

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

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

469

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

Однако, как и всегда в Swing, у вас есть возможность воспользоваться стандартными редакторами, поставляющимися вместе с библиотекой. Зачастую их возможностей вполне хватает для вашего приложения, а настраивать их и затем работать с ними совсем просто. Стандартный редактор для деревьев (кстати, тот же редактор применяется и для таблиц) реализован в классе DefaultCellEditor. Он позволяет организовать редактирование значений с помощью одного из трех компонентов: текстового поля JTextField, раскрывающегося списка JComboBox или флажка JCheckBox. Действие происходит следующим образом: вы создаете компонент для редактирования, настраиваете его (например, заполняете список элементами) и передаете в соответствующий конструктор стандартного редактора. Далее остается включить редактирование дерева и присоединить редактор методом setCellEditor(). Дерево JTree будет готово использовать настроенный вами стандартный редактор.

В пакете javax.swing.tree есть вспомогательный редактор DefaultTreeCellEditor. Он, хотя и реализует все предписанные интерфейсом TreeCellEditor методы, сам ничего не редактирует, а передает свои обязанности «настоящему» редактору, который вы можете задать в конструкторе. В качестве «настоящего» редактора, как правило, применяется только что описанный нами редактор DefaultCellEditor. Главным его достоинством является то, что он производит редактирование узла дерева, не убирая с экрана значок, ассоциированный с этим узлом. Такое поведение более понятно пользователю, чем исчезновение значка при редактировании и восстановление его на экране после окончания редактирования.

В следующем несложном примере попробуем применить стандартный редактор для дерева:

//TreeDefaultEditing.java

//Стандартные редакторы для деревьев import javax.swing.*;

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

public class TreeDefaultEditing extends JFrame { // листья дерева храним в массивах

private String[] basics = { "Красный", "Зеленый", "Синий" };

private String[] extendeds = { "Желтый", "Голубой", "Розовый" };

public TreeDefaultEditing() { super("TreeDefaultEditing"); setDefaultCloseOperation(EXIT_ON_CLOSE);

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

JTree tree = new JTree(createTreeModel());

//включаем редактирование

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