Часть 4 про flash9 и aswing
Материал из Dom.
[править] Дорога из желтого кирпича: строим пользовательские интерфейсы вместе с Flash 9 & ASWing. Часть 4
Сегодня мы закончим знакомство с библиотекой визуальных элементов управления ASWing3. Нам осталось только научиться работать с таким “сложным” элементом управления как дерево. Также я покажу, как пользоваться в ASwing методами DnD. И как вы можете создавать для своих компонентов всплывающие подсказки (tooltip).
Последний сложный компонент, с которым мы познакомимся, это дерево или JTree. Вы знаете, что деревья служат для отображения иерархической информации (например, дерево каталогов в проводнике или финансовые ведомости, сгруппированные по годам, месяцам или отделам). Такое визуальное представление информации очень удобно. К сожалению, я многократно видел, как начинающие разработчики пытаются избежать применения данного компонента, обосновывая это сложностью работы с рекурсивно организованными структурами данных. Посмотрим, что вы скажете, когда познакомитесь с реализацией дерева в ASWing. Когда я оцениваю некоторый продукт, меня очень интересует, как он работает с ресурсами. Что будет если в это дерево поместить пару тысяч узлов? Насколько оптимальны внутренние алгоритмы работы, не приведет ли попытка развернуть узел к длительному ожиданию, или даже аварийному прерыванию работы скрипта (я еще помню как печально во времена flash8 или flash2004 заканчивался такой эксперимент). Обновленный flashplayer 9 гораздо производительнее своих предшественников, но если в реализации ASwing есть ошибки, то исправить их он все равно не в силах. В прошлый раз, когда мы познакомились с компонентом-таблицей JTable, я для эксперимента загружал в нее случайно сгенерированные данные объемом до 100 000 строк. Затраты времени были ничтожны, а скроллинг по таблице был гладким и без задержек. Остается проверить качество реализации и для JTree. А учитывая, что из версии flash cs3 компонент Tree куда-то пропал (он доступен только для документа в стиле actionscript2, но actionscript3), то рассказать об этом компоненте я уже просто обязан.
Прежде всего, перед тем как вы создадите дерево вам необходимо спланировать и реализовать модель данных. Объект этой модели следует передать в качестве параметра конструктору JTree. В роли модели данных может выступать любой класс, поддерживающий интерфейс TreeModel. В составе ASWing уже есть стандартная заготовка в виде класса DefaultTreeModel. Именно его мы и будем использовать. Из чего состоит дерево? Очевидно, что из множества иерархически упорядоченных узлов. Каждый узел это объект некоторого класса поддерживающего интерфейс TreeNode. И как уже стало привычным, в составе ASwing уже есть класс-заготовка, поддерживающий данный интерфейс, который мы и будем использовать. Это класс DefaultMutableTreeNode. Слово “Mutable” в его названии говорит то, что узлы этого типа способы изменять свое содержимое. Так вы можете добавлять к узлу дочерние узлы, и это стразу же будет отображено в составе компонента JTree, построенного на базе этой модели данных. С другой стороны если вам нужно построить огромное статическое дерево, то лучше отказаться от DefaultMutableTreeNode в пользу собственной оптимизированной версии класса узла.
Что умеет делать DefaultMutableTreeNode и как нам его использовать? Помните, в прошлый раз я вам уже рассказывал об идее MVC и отделении информации от ее представления (внешнего вида)? Так вот объект DefaultMutableTreeNode выступает в роли обертки для “пользовательского объекта”. Именно пользовательский объект это информация, ссылку на него следует передать конструктору DefaultMutableTreeNode. Создав узел необходимо указать его место в общей иерархии узлов. Т.е. какой узел будет считаться для него родительским, а какие узлы будут уже дочерними. Хотя на сайте библиотеки ASWing я не нашел примеров посвященных данному компоненту, но учитывая что ASwing – это калька со Swing из мира java, то вам должно понравиться такое пособие: http://java.sun.com/docs/books/tutorial/uiswing/components/tree.html.
Также как и для JTable рекомендуется помещать Jtree внутрь области прокрутки JScrollPane. В примере ниже я покажу, как создать модель данных для дерева, наполнить ее узлами и визуально ее отобразить. Для этого примера не хватает только хорошего источника данных. Добраться до файловой системы компьютера нам не удастся. Методов чтения информации о дереве из документа xml просто нет (позор-позор, надеюсь, что эта возможность будет реализована как можно скорее, все-таки в мире flash возможность обмена данным между сервером и клиентским роликом-приложением большей частью реализована именно через передачу xml). Так что я создам три класса TDepartment, THuman, TSalary, хранящих информацию соответственно об отделе, сотруднике и его зарплате. Каждый из этих узлов будет иметь свое особое визуальное оформление. Обратите внимание на то, что эти классы должны быть связаны между собой отношением подчиненности. Так в составе отдела есть некоторый список сотрудников, а у каждого сотрудника есть перечень ведомостей на зарплату. Значит, мне добавить для этих классов поля хранящие ссылку на родительский объект, а также список дочерних. По-сути, такая структура данных дублирует собственное устройство класса DefaultMutableTreeNode. А раз так, то я создам информационные классы как наследники от DefaultMutableTreeNode. Сначала создайте три файла TDepartment.as, THuman.as, TSalary.as, в которых поместите следующий код:
package { import org.aswing.*; import org.aswing.tree.*; public class TDepartment extends DefaultMutableTreeNode{ public function TDepartment(_caption : String) { super (_caption); } } } //-- следующий класс ---- package { import org.aswing.*; import org.aswing.tree.*; public class THuman extends DefaultMutableTreeNode{ public function THuman(_fio : String) { super (_fio); } } } // --- следующий класс --- package { import org.aswing.*; import org.aswing.tree.*; public class TSalary extends DefaultMutableTreeNode{ public function TSalary(when : Date, amount : Number){ super ({when : when , amount : amount}); // и иные действия по инициализации сведений об отделе предприятия } } } Теперь можно создать и саму модель данных: // создаем корневой элемент внутри которого и будут размещены отделы предприятия var theroot = new DefaultMutableTreeNode ("MMM ltd."); // создем объекты узлы для предприятий var finance = new TDepartment ("Finance dept."); var management = new TDepartment ("Management dept."); var secret = new TDepartment ("TopSecret dept."); // теперь надо создать сотрудников var vasya = new THuman ("Vasya"); // и для каждого из них указать сведения об зарплате vasya.append (new TSalary (new Date (2006, 1, 1) , 2000) ); vasya.append (new TSalary (new Date (2006, 2, 1) , 3000) ); var bill = new THuman ("Bill"); bill.append (new TSalary (new Date (2005, 1, 1) , 2000) ); // помещаем сотрудников внутрь отделов finance.append (vasya); management.append (bill); // добавляем отделы внутрь корневого узла theroot.append (finance); theroot.append (management); theroot.append (secret); // создаем модель данных из корневого узла var model:DefaultTreeModel = new DefaultTreeModel (theroot); // создаем и отображаем дерево var tree = new JTree(model); panel_top.append(new JScrollPane (tree));
Результат работы скрипта показан на рис. 1.
Заметьте что для узла “департамент” и “сотрудник” название узла совпало со значением их наименований. Если вы посмотрите внимательно на конструктор этих классов, то увидите вызов “super(…)”. Вместо троеточия подставляется имя или название отдела – это и есть пользовательский объект. Когда необходимо сформировать текстовую надпись для узла, то выполняется преобразование объекта в строку с помощью метода toString. И если вы его переопределите, например, так (это следует делать только для класса TSalary), то внешний вид надписи будет уже такой “salary: at Wed Feb 1 00:00:00 GMT+0200 2006 = 2000”:
public override function toString():String { var uo = getUserObject(); return "salary: at " + uo.when + " = " + uo.amount;}
И последний шаг. Давайте создадим собственный render, который будет перед текстовым содержимым узла выводить дополнительную иконку в зависимости от его типа. Для этого вам необходимо создать объект класса GeneralTreeCellFactory. Этот класс является фабрикой производящей для дерева графическое представление узла, которое в свою очередь должно поддерживать интерфейс TreeCell. Момент в том, что вам необходимо будет реализовать различный внешний вид ячейки, в разных состояниях: “свернуто”, “развернуто”, “узел выбран”, “узел является конечным листом дерева”, поэтому имеет смысл создавать собственный класс производным от вспомогательного (уже содержащего реализацию типового отображения узла) DefaultTreeCell. Обязательно посмотрите исходники этого класса, без этого вы не поймете многое из того что я буду делать далее. Я перекрою метод “setTreeCellStatus”, вызываемый всякий раз, когда состояние узла меняется. Если узел служит для отображения информации о TSalary, то мне не нужно выполнять модификации иконок, как для иных узлов. Также мне нужно перекрыть метод “public override function setCellValue(value:*):void ” . Внутри этого метода я буду проверять, что это за тип узла и если “пользовательский объект” узла принадлежит к классу TSalary, то выполню модификацию внешнего вида своего родительского компонента DefaultTreeCell. Схожую методику я использовал для своих “серьезных проектов”, там я создал надстройку над TreeCellFactory, для которой можно было выполнить регистрацию классов-рендереров по явно заданному типу, примерно так (осторожно, это псевдо-код и он не работает):
var myfactory = new MyDynFactory (); myfactory.registerType (TDepartment, TDepertmentRenderer); myfactory.registerType (THuman, THumanRenderer); // по аналогии я выполняю регистрацию для всех типов данных узлов …. // и назначаем созданную фабрику дереву Mytree.setCellFactory (myfactory);
Попробуйте самостоятельно реализовать такую надстройку, это будет хорошим упражнением на работу с паттернами и reflection api. Теперь я приведу пример кода класса TMyCellRenderer:
package { import org.aswing.*; import org.aswing.tree.*; public class TMyCellRenderer extends DefaultTreeCell { public override function setTreeCellStatus(tree:JTree, selected:Boolean, expanded:Boolean, leaf:Boolean, row:int):void { if (!(value is TSalary)) { super.setTreeCellStatus(tree, selected , expanded , leaf, row); } } public override function setCellValue(value:*):void { if (value is TSalary) { this.value = value; var uo = (value as TSalary).getUserObject(); super.setText("Salary: " + uo.when + " = " + uo.amount); super.setIcon(uo.amount > 2000?ico1:ico2); } else { super.setCellValue(value); } } private var ico1 = new LoadIcon ("ico_fla9_1.PNG"); private var ico2 = new LoadIcon ("ico_fla9_2.PNG"); public override function getCellComponent():Component { return this; } } }
Теперь надо назначить созданный рендерер для собственно компонента дерева. Используйте фабрику конструирующую объекты ячейки по заданному как параметр конструктора типу данных. tree.setCellFactory (new GeneralTreeCellFactory (TMyCellRenderer) );
Результат работы скрипта показан на рис. 2.
Обратите внимание на то, что иконка отличается в зависимости от размера зарплаты у сотрудника. А как насчет скорости работы, о важности которой я говорил в начале? Проведя несложный эксперимент, я установил, что дерево размеров в 50 000 узлов не приводит к потере скорости работы. И все благодаря умному созданию renderer-ов для ячеек (они создаются только перед непосредственно показом на экране).
Следующий компонент, который мы рассмотрим это ToolTip. Но это не привычная всем простая подсказка в виде текстовой надписи. В aswing вы можете использовать в качестве подсказки произвольный графический компонент. Например в следующем примере для того чтобы отобразить подсказку для кнопки я использую … компонент дерево. А почему бы и нет? С другой стороны, если вам подходит традиционный вид подсказки, то вы можете просто-напросто, для произвольного компонента назначить ее с помощью вызова метода “setToolTipText ”.
var panel_top:JPanel = new JPanel(); panel_top.setLayout(new BorderLayout() ); var btn1 = new JButton ("Click me !"); btn1.setToolTipText ("It's Simple Hint About This Button"); var btn2 = new JButton ("Dont't Click me !"); var tip1:JToolTip = new JToolTip(); // конкретное значение подсказки совершенно не важно, однако если его не указать то // подсказка не будет появляться tip1.setTipText ("bla-bla-bla"); tip1.removeAll (); tip1.append (new JTree ()); tip1.setSizeWH(400, 300); tip1.setTargetComponent(btn2); panel_top.append (btn1, BorderLayout.SOUTH); panel_top.append (btn2, BorderLayout.NORTH);
Результаты работы сприпта приведены на рис. 3.
Если немного подправить исходный код класса JToolTip, то можно сделать, так что ваша “продвинутая” подсказка не будет исчезать при малейшем движении мыши, а например, будет уметь взаимодействовать с пользователем. Например, сделайте подсказку, которая будет содержать небольшой текст, сверху ее будет набор кнопок для перехода к более полному разделу справки по программе. С другой стороны если пользователь навел мышь на другой компонент программы, то подсказку все же следует прятать.
Последнее что мы сегодня рассмотрим это технология DnD (перетяни-и-брось). За ее реализацию отвечает пакет org.aswing.dnd. Если вы хотите, чтобы некоторый компонент можно было перетягивать, то вам следует вызвать для него метод setDragEnabled (true). Затем для того компонента, на который можно перетянуть что-либо, следует разрешить ему генерировать события DnD-цикла с помощью метода setDropTrigger (true). А также следует зарегистрировать те компоненты, которые должны перетягиваться с помощью addDragAcceptableInitiator. После чего надо создать обработчики событий стадий DnD. Эти функции будут вызваны в самом начале процесса DnD, во время его, и при завершении. Объект, в составе которого эти функции реализованы, следует зарегистрировать для того, чтобы менеджер DnD знал кого извещать об этих событиях с помощью вызова (объект обязательно должен поддерживать интерфейс DragListener):
Результат работы нижеприведенного скрипта показан на рис. 4.
Сначала я привожу код класса DnDHadler:
package { import org.aswing.*; import org.aswing.event.*; import org.aswing.dnd.*; public class DnDHadler implements DragListener { public function onDragStart(e:DragAndDropEvent):void {} public function onDragEnter(e:DragAndDropEvent):void {} public function onDragOverring(e:DragAndDropEvent):void {} public function onDragExit(e:DragAndDropEvent):void {} public function onDragDrop(e:DragAndDropEvent):void { var targetComponent:Component = e.getTargetComponent(); var dragInitiator:Component = e.getDragInitiator(); if (targetComponent.isDragAcceptableInitiator(dragInitiator)) { var ct:Container = Container(targetComponent); dragInitiator.removeFromContainer(); ct.append(dragInitiator); ct.removeDragAcceptableInitiator(dragInitiator); } else { DragManager.setDropMotion(new RejectedMotion()); } //dragInitiator.revalidate(); } } } //А теперь пример кода создающего две панели на форме и //доступной для перетягивания (увы, только в одном направлении) надписи JLabel: var panel_top = new JPanel (new BorderLayout ()); // создаем две панели, одна из них будет источником, вторая место назначением var destPane = new JPanel(new FlowLayout()); destPane.setDropTrigger(true); // разрешаем перетягивать на панель другие объекты destPane.setBorder (new TitledBorder (null, "Destination")); // создаем для двух панелей визуальные границы var srcPane = new JPanel(new FlowLayout()); srcPane.setBorder (new TitledBorder (null, "Source")); var lab = new JLabel("Try Drag me"); lab.setDragEnabled(true); // разрешаем выполнять перетаскивание надписи destPane.addDragAcceptableInitiator(lab); // добавляем обработчик события начало перетаскивания lab.addEventListener(DragAndDropEvent.DRAG_RECOGNIZED, __startDrag); srcPane.append (lab); panel_top.append (srcPane , BorderLayout.SOUTH); panel_top.append (destPane , BorderLayout.NORTH); // добавляем на stage корневой элемент панели addChild(panel_top); // запускаем перерасчет положения элементов panel_top.validate(); function __startDrag(e:DragAndDropEvent):void { // здесь мы запускаем процесс перетаскивания со специально созданным объектом DnDHadler DragManager.startDrag(e.getDragInitiator(), null, null, new DnDHadler ()); }
Эта статья последняя в серии посвященной библиотеке ASwing. Надеюсь, что библиотека будет продолжать развиваться, а вы будете ей пользоваться. Сама же серия “Дорога из желтого кирпича” естественно будет продолжена. В будущих выпусках я расскажу о языке haxe и среде разработки flashdevelop.
|
|
Subscribe Now! |
|


