Уход за деревьями |
445 |
дель может использовать в качестве узлов любые объекты, реализующие интерфейс TreeNode, но нетрудно догадаться, что чаще всего ей передаются экземпляры класса DefaultMutableTreeNode. Стандартная модель и заботится о том, чтобы организованная нами структура вписывалась в рамки интерфейса TreeModel и выводилась деревом на экран.
Стандартная модель DefaultTreeModel
Итак, стандартная модель дерева DefaultTreeModel хранит иерархические структуры, образованные узлами TreeNode. Для создания стандартной модели достаточно передать
вее конструктор узел TreeNode, являющийся корнем дерева, а обо всем остальном модель позаботится сама, так что дерево, начинающееся с этого узла, будет правильно выведено на экран. Дополнительных возможностей в стандартной модели практически нет, и это неудивительно: все, что вам может понадобиться при работе с деревом, уже есть
вузлах DefaultMutableTreeNode (ну а если вы реализуете интерфейс TreeNode самостоятель-
но, то, скорее всего, на это есть веская причина, и нужные вам возможности у вас уже есть). Рассмотрим пример использования стандартной модели, который заодно станет первым примером применения узлов DefaultMutableTreeNode:
//UsingDefaultTreeModel.java
//Использование стандартной модели дерева и
//узлов DefaultMutableTreeNode
import javax.swing.*; import javax.swing.tree.*; import java.awt.*;
public class UsingDefaultTreeModel extends JFrame { // для удобства листья будем хранить в массивах
private String[] drinks = { "Коктейль", "Сок", "Морс" }; private String[] fruits = { "Яблоки", "Апельсины" }; public UsingDefaultTreeModel() {
super("UsingDefaultTreeModel"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем нашу древовидную структуру
DefaultMutableTreeNode root =
new DefaultMutableTreeNode("Корень дерева");
//основные ветви
DefaultMutableTreeNode drink =
new DefaultMutableTreeNode("Напитки"); DefaultMutableTreeNode fruit =
new DefaultMutableTreeNode("Фрукты");
//добавляем ветви root.add(drink); root.add(fruit);
//специальный конструктор
root.add(new DefaultMutableTreeNode("Десерт", true));
446 ГЛАВА 15
// добавляем листья
for (String _drink : drinks) { drink.add(
new DefaultMutableTreeNode(_drink, false));
}
for (String _fruit : fruits) { fruit.add(
new DefaultMutableTreeNode(_fruit, false));
}
//создаем стандартную модель и дерево
DefaultTreeModel dtm1 =
new DefaultTreeModel(root, true); JTree tree1 = new JTree(dtm1);
//модель можно создать, начиная с любого узла
DefaultTreeModel dtm2 = new DefaultTreeModel(drink); JTree tree2 = new JTree(dtm2);
//добавляем деревья в окно и показываем его setLayout(new GridLayout(1, 2));
add(new JScrollPane(tree1)); add(new JScrollPane(tree2)); setSize(400, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new UsingDefaultTreeModel(); } });
}
}
В примере мы создаем древовидную структуру на основе стандартных узлов DefaultMutableTreeNode так, как мы и обсуждали: сначала создается корень нашего дерева (объект root), к которому присоединяются его потомки (узлы с информацией о доступных нам напитках и фруктах, а также десерте). Далее мы присоединяем к этим узлам (первого уровня, корень является узлом нулевого уровня, кстати, уровень любого узла позволяет узнать полезный метод getLevel() класса DefaultMutableTreeNode) их потомков, которые для удобства хранятся в массивах и добавляются к дереву в цикле. Заметьте, что для узла с информацией о десерте мы использовали особенный конструктор: в качестве параметров он принимает не только данные, которые будет хранить узел, но еще и булево значение. Последнее управляет тем, как будет вести себя метод getAllowsChildren(), определенный в интерфейсе TreeNode. Если он возвращает true, это означает, что у данного узла могут быть потомки, даже если их в данный момент нет (такое поведение характерно, например, для пустого каталога без файлов, который, тем не менее, остается каталогом и в будущем может содержать приличное количество файлов). В стандартной модели DefaultTreeModel поддерживаются два способа, позволяющие различать листья дерева (узлы без потомков). Согласно первому способу считается, что узел является листом, если у него в дан-
Уход за деревьями |
447 |
ный момент нет потомков, а согласно второму узел не является листом, если его метод getAllowsChildren() возвращает true (то есть потомки могут появиться). Для включения второго способа нужно использовать специальный конструктор класса DefaultTreeModel, передав ему значение true в качестве второго параметра (что мы и сделали для первой модели в примере) или вызвав метод модели setAsksAllowsChildren(true). В этом случае узел «с десертом» будет выглядеть как «папка», так оно и должно быть, поскольку обычно предоставляется несколько видов десерта. Подобное поведение может пригодиться в программах довольно часто: в деревьях встречаются «ненастоящие» листья, в которых потомков нет, но они могут появиться, и их нужно отличать от листьев «настоящих».
Вторая модель дерева создается, начиная не с корня, а с узла drink, так что вы свободно можете показывать свое дерево по частям, если в этом возникнет необходимость. Во второй модели у нас нет пустых «папок», поэтому мы не используем второй параметр конструктора, и листами считаем все узлы, у которых нет потомков. Для того чтобы дерево начало отображать данные созданных нами стандартных моделей, достаточно передать их в соответствующий конструктор класса JTree (или вызвать метод setModel(), если вы предпочитаете другие конструкторы). После этого остается разместить деревья
вокне (мы разделяем окно на две равные части с помощью менеджера табличного расположения GridLayout и размещаем в них наши деревья, предварительно «обернутые»
впанели прокрутки JScrollPane) и вывести последнее на экран.
После того как вы насладитесь напитками и фруктами, представленными с помощью стандартной модели и дерева JTree, может возникнуть вопрос: а как у стандартной модели обстоит дело с динамическим изменением узлов и потомков? И здесь стандартная модель на высоте. У вас есть целый арсенал методов для динамической манипуляции деревом, начиная от полной смены корня (методом setRoot()), вставки (специальным методом insertNodeInto()) узла типа MutableTreeNode в узел такого же типа на произвольную позицию с автоматическим оповещением слушателей и заканчивая простым изменением созданных вами узлов DefaultMutableTreeNode, которые, как мы прекрасно знаем, очень легко изменять и обновлять.
Если вы меняете узлы, не касаясь модели, ей надо об этом сообщить, чтобы она, в свою очередь, сообщила дереву о необходимости перерисовки. Для этого можно воспользоваться методом reload() модели, сообщающим слушателям об изменении всей модели (впрочем, есть перегруженная версия этого метода, сообщающего об изменениях в определенном узле дерева), или обратиться к методам вида nodesXXX(), которых в стандартной модели несколько. Эти методы (например, метод nodesChanged()) позволяют со-
448 |
ГЛАВА 15 |
общать об изменениях в потомках некоторого узла и экономят время обновления дерева, сообщая только о действительно измененных узлах. «Точечное» обновление дерева очень важно для высокой производительности перерисовки и мгновенной реакции на действия пользователей, которые всегда в восторге от быстрых приложений.
Таблица 15.1. Методы стандартной модели для оповещения об обновлении данных
Методы |
Описание |
reload() |
Говорит модели о том, что полностью поменялся некоторый узел |
|
и его необходимо перезагрузить и перерисовать. Метод без параме- |
|
тров перезагружает все дерево начиная с корня. Однако если у вас |
|
не меняется структура узлов, лучше применять методы, описанные |
|
ниже, потому что после перезагрузки узла он будет полностью закрыт, |
|
так что пользователь потеряет из виду все узлы, которые он открыл |
|
вручную |
nodeStructureChanged() |
Полный аналог предыдущего метода с другим названием |
nodesWereInserted(), |
Говорит модели о том, что для указанного в качестве параметра узла- |
nodesWereRemoved() |
предка были добавлены или удалены узлы-потомки. Это более эф- |
|
фективные сообщения, чем два, описанные выше |
nodesChanged() |
Метод сообщает модели (а та дереву), что поменялось значение |
|
указанного узла, так что его необходимо перерисовать. Структура |
|
дерева при этом не меняется, что делает данный вызов самым эф- |
|
фективным. Именно его следует предпочитать при желании обновить |
|
внешний вид узла после обновления его данных. |
Для того чтобы убедиться в том, что «точечное» обновление модели позволяет получить высокую производительность даже если приходится постоянно обновлять изображение дерева на экране, напишем небольшой пример. В нем мы создадим очень большое, нагруженное узлами дерево, и заставим все узлы постоянно обновляться. Как правило, именно так ситуация и обстоит в приложениях, работающих с большими объемами данных. Посмотрим, что у нас получается:
//TreeModelUpdates.java
//Пример эффективного обновления большого
//дерева с переменными данными
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath;
import javax.swing.*;
import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.*;
public class TreeModelUpdates extends JFrame { // дерево
private JTree tree;
// стандартная модель дерева private DefaultTreeModel model;
Уход за деревьями |
449 |
public TreeModelUpdates() { super("TreeModelUpdates"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем дерево на основе модели
tree = new JTree(model = createTreeModel()); add(new JScrollPane(tree));
setSize(400, 300); setVisible(true);
}
// создание несложной, но большой модели дерева private DefaultTreeModel createTreeModel() {
//корень нашего дерева
DefaultMutableTreeNode root = new DefaultMutableTreeNode(
"Большоооое Дерево");
//присоединяем листья
for (int i=0; i<100; i++) {
DefaultMutableTreeNode node = new ChangeableColorNode(); root.add(node);
for (int k=0; k<10; k++) { node.add(new ChangeableColorNode());
}
}
// создаем стандартную модель return new DefaultTreeModel(root);
}
// узел дерева, динамически обновляющий свое содержимое class ChangeableColorNode
extends DefaultMutableTreeNode implements ActionListener { // случайная задержка обновления
private int delay = (int) (Math.random()*3000 + 500); // цвета текста, хранимого в узле
private int r,g,b;
public ChangeableColorNode() {
// генерируем цвета и запускаем таймер обновления generateColors();
new Timer(delay, this).start();
}
// этот метод вызывается таймером
public void actionPerformed(ActionEvent e) { // обновляем цвета и текст