« Разработка веб-страниц с помощью google gears. Часть 3 | Разработка веб-страниц с помощью google gears. Часть 4 » |
Про java swing - часть 5
Деревья
Второй элемент управления активно используемый при разработке бизнес-приложений - это дерево (JTree). Мы используем деревья для отображения иерархической информации. В практике часто используется и сочетание JTree и JTable в одном элементе управления. Хотя в swing такой функциональности изначально не предусмотрено, однако на сайте sun в уроках по swing рассматривается достойный пример создания JTreeTable.
Рассмотрим как создать дерево: простейший способ это сделать - задать конструкторе JTree иерархию объектов (массивов). Каждый объект в таком массиве будет представлен как отдельный узел дерева:

public static void main(String[] args) {
class Node {
String name;
public Node(String name) {
this.name = name;
}
// обратите внимание на 'перекрытый' метод toString
// именно это значение будет возвращено и отображено в метке узла дерева
public String toString() {
return "Node Item: " + name;
}
}
JFrame jf = new JFrame("Tree Demo 1");
JTree jt = new JTree(new Object[]
{"Apple", "Orange", "Grapes",
// вложенный массив элементов
new Object[]{
"Spartak",
"Caesar",
"Mark"
},
"Mikki Mouse", "Donald Duck", "Pluto",
new Node("Joe")
});
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.getContentPane().add(new JScrollPane(jt));
jf.pack();
jf.setVisible(true);
}
public class FileTree {
public static void main(String[] args) {
JFrame jf = new JFrame();
File[] froots = File.listRoots();
// получаем список 'корней' файловой системы.
// для windows - это имена дисков: a:, c:, d: e:
// для linux - /
DefaultMutableTreeNode super_root = new DefaultMutableTreeNode("My Computer");
// создаем узел являющийся корнем дерева
for (int i = 0; i < froots.length; i++) {
File froot = froots[i];
// для каждого корня файловой системы создается собственный узел дерева JTree
DefaultMutableTreeNode mroot = new DefaultMutableTreeNode(froot);
// затем он помещается внутрь корневого узла
super_root.add(mroot);
// и вызываем рекурсивную процедуру (да, да это так ужасно),
// которая заполняет текущий узел (корень файловой системы)
// некоторым количеством вложенных узлов.
// глубина сканирования строго ограничена
addChildrensToNode(0, mroot, froot);
}
// содаем графическое представление дерева
final JTree jt = new JTree(super_root);
// указываем для этого дерева Renderer
jt.setCellRenderer(
new TreeCellRenderer() {
// заготовка возвращаемого из renderer-а компонента
JLabel jLabel = new JLabel("XXX");
public final ImageIcon ICON_FILE = new ImageIcon("c:\\tmp\\file.png");
public final ImageIcon ICON_DIR = new ImageIcon("c:\\tmp\\dir.png");
public final ImageIcon ICON_MYCOMP = new ImageIcon("c:\\tmp\\comp.png");
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
// опять внимание на параметры метода:
// узел дерева - передается вовсе не пользовательская информация хранящаяся внутри узла,
// а именно сам узел.
// затем признаки того, выделен ли данный элемент, развернут ли узел,
// является ли данный узел конечным листом,
// затем координаты узла (номер строки) и наконец получил ли данный элемент фокус ввода
DefaultMutableTreeNode mtree = (DefaultMutableTreeNode) value;
// в дальшейшем из полученного узла mtree извлекается и используется
// пользовательский объект с помощью getUserObject
if (mtree.getUserObject() instanceof String) {
// особая проверка необходимая для того,
// чтобы корректно обработать корневой элемент дерева (хранящий не файл и не каталог)
// а, именно, текстовую надпись "My Computer"
jLabel.setText(mtree.getUserObject().toString());
jLabel.setIcon(ICON_MYCOMP);
return jLabel;
}
File f = (File) mtree.getUserObject();
// в противном случае, содержимое узла файл, то мы должны его соответствующим образом отобразить
try {
// в зависимости от того какое имя данного файла, мы возвращаем соответствующую надпись JLabel.
// также изменяем и видимую иконку
jLabel.setText(f.getCanonicalPath());
jLabel.setIcon(f.isDirectory() ? ICON_DIR : ICON_FILE);
return jLabel;
} catch (IOException e) {
e.printStackTrace();
}
return new JLabel("Ошибка");
}
}
);
jt.setLargeModel(true);
jf.getContentPane().add(new JScrollPane(jt));
JLabel lab_hint = new JLabel("Подсказка");
JScrollPane comp = new JScrollPane(lab_hint);
jf.getContentPane().add(comp, BorderLayout.SOUTH);
jf.pack();
jf.setVisible(true);
}
public static final int MAX_LEVELS = 2;
// данная переменная хранит значение максимального уровня для рекурсивного просмотра каталога
private static void addChildrensToNode(int level, DefaultMutableTreeNode mroot, File froot) {
if (level >= MAX_LEVELS) return;
File[] fall = froot.listFiles();
if (fall == null) return;
for (int i = 0; i < fall.length; i++) {
File file = fall[i];
DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(file);
mroot.add(newChild);
if (file.isDirectory())
addChildrensToNode(level + 1, newChild, file);
}
}
}

Для представления узла дерева наиболее удобно использовать класс DefaultMutableTreeNode. Этот класс содержит внутри себя пользовательский объект (информационную составляющую узла) и предоставляет список методов для управления: добавление, удаление ... для дочерних элементов. Также предоставляются простейшие средства для обхода деревьев деревьев (помните из курса СДА все эти восходящие и нисходящие, слева-направо и наоборот ...).
Метод | Описание |
void add(MutableTreeNode newChild) | Добавить к узлу новый дочерний элемент. Элемент добавляется в конец списка дочерних узлов. Если же раньше данный элемент-узел был дочерним по отношению к чему-то еще, то старая связь разрывается. |
Enumeration breadthFirstEnumeration() | Возвращается интерфейс перечисления всех дочерних элементов в порядке обхода в ширину. |
Enumeration children() | Перечисление всех дочерних элементов (только непосредственно дочерние узлы). |
Enumeration depthFirstEnumeration() | перечисление всех дочерних элементов, но уже в порядке обхода в глубину. |
boolean getAllowsChildren() | метод возвращает признак того, может ли данный узел иметь дочерние элементы. |
TreeNode getChildAt(int index) | Поиск дочернего элемента по номеру. |
int getChildCount() | Возвращается количество дочерних элементов. |
int getDepth() | Возвращается глубина узла. Она расчитывается как наибольшая длина между текущим элементом и всеми его листами т.е. терминальными узлами. |
TreeNode getFirstChild() | Вернуть первый из дочерних элементов. |
int getIndex(TreeNode aChild) | Поиск по значению узла его порядкового номера. |
TreeNode getLastChild() | Вернуть последний узел среди дочерних. |
int getLevel() | Вернуть номер уровня (растояние от корня дерева до текущего элемента). |
TreeNode getParent() | Вернуть ссылку на родительский элемент. |
TreeNode[] getPath() | Возвращается массив узлов, через которые необходимо пройти, чтобы добраться до текущего узла. |
TreeNode getRoot() | Возвращается ссылка на корневой элемент для всего дерева. |
int getSiblingCount() | Возвращается количество сиблингов (“братских” или “сестерских” элементов для текущего узла). |
Object getUserObject() | Метод возвращает ссылку на пользовательский объект, хранящийся внутри узла. |
Object[] getUserObjectPath() | Метод похож на ранее рассмотренный getPath, но возвращается не массив узлов, а массив пользовательских объектов, хранящихся внутри узлов на пути от корня дерева до текущего элемента. |
void insert(MutableTreeNode newChild, int childIndex) | Добавить новый элемент как дочерний к текущему. Причем необходимо поместить элемент под указанным номером. |
boolean isLeaf() | Вернуть признак того, является ли данный узел терминальным (т.е. листом не имеющим дочерних узлов). |
boolean isRoot() | Вернуть признак того, что узел является корневым. |
Enumeration preorderEnumeration() | Снова возвращается список всех дочерних узлов для некоторого узла дерева, но обход дерева будет выполнен в порядке "обратный нисходящий". |
void remove(int childIndex) | Выполняется удаление одного из дочерних элементов под указанным номером. |
void remove(MutableTreeNode aChild) | Тоже что и предыдущий метод, но удаляем не по номеру, а по значению самого узла. |
void removeAllChildren() | Уничтожить все дочерние элементы. |
void setParent(MutableTreeNode newParent) | Переместить узел к другому родительскому узлу. |
void setUserObject(Object userObject) | Установить значение пользовательского объекта. |
Выделение узлов дерева. Обработка событий "сворачивание" и "развертывание" узлов дерева
Для реации на то, как происходит выделение узлов и путей в дереве, используется концепция "модели выделения и слушателей событий выделения". Ниже приводится пример в развитие ранее приведенного (примера строящего дерево файловой системы). Ниже дерева добавляется надпись Jlabel, в которую выводится путь к выделенному элементу. А также пример демонстрирует как реагировать на сворачивание и развертывание узлов дерева, и то как мы можем разрешать или запрещать данную операцию.
// теперь к дереву нужно добавить слушателей
jt.addTreeExpansionListener(
new TreeExpansionListener() {
public void treeExpanded(TreeExpansionEvent event) {
// эти слушатели извещаются когда операция была завершена. Здесь раскрытие узла.
JOptionPane.showMessageDialog(jt, event.getPath().getLastPathComponent());
}
public void treeCollapsed(TreeExpansionEvent event) {
// эти слушатели извещаются когда операция была завершена. Здесь свертывание узла.
JOptionPane.showMessageDialog(jt, event.getPath().getLastPathComponent());
}
}
);
jt.addTreeWillExpandListener(
new TreeWillExpandListener() {
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
// разрешено ли развернуть узел
System.out.println ("treeWillExpand");
if (Math.random() < 0.5)
throw new ExpandVetoException(event, "увы, но развернуть узел дерева не возможно");
}
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
// разрешено ли свернуть узел
System.out.println("treeWillCollapse");
}
}
);
Как небольшое задание перепишите ранее приведенный пример построения файлового дерева, заменив рекурсивное сканирование файловой системы на "более умный подход". Вспомните, как мы изучая MFC делали похожий пример с деревом и CTree. Тогда дерево строилось только на один уровень вложенности, а недостающие узлы добавлялись динамически как только пользователь хотел развернуть некоторый узел, то мы обрабатывали событие "ДО РАЗВЕРТЫВАНИЯ" и достраивали дерево до нужного размера.
« Разработка веб-страниц с помощью google gears. Часть 3 | Разработка веб-страниц с помощью google gears. Часть 4 » |