Дорога из желтого кирпича: строим пользовательские интерфейсы вместе с Flash 9 & ASWing. Часть 2

Сегодня мы продолжаем знакомство с библиотекой визуальных элементов управления ASWing3. Эта opensource библиотека позволяет легко создавать интерфейсы для основанных flash приложений. В прошлый раз мы получили представление о концепции раскладок, управляющих расположением элементов. Познакомились с методикой создания диалоговых окон, создавали текстовые поля и кнопки. Сегодня мы рассмотрим несколько элементов-контейнеров, завершим обзор простых элементов управления, научимся работать с меню.

Итак, любое приложение сложнее калькулятора использует для удобного построения интерфейса с пользователем несколько специализированных окон. Главное окно приложения может содержать набор кнопок, вызывающих более специализированные окна, например, окно настроек приложения, окно выбора шрифта. Часть таких окон мы создаем сами, частью же используем встроенные в операционную систему возможности. Для flash существуют определенные ограничения, связанные с тем, что первоначально flash разрабатывался для исполнения в среде браузера и методы запроса стандартных услуг у операционной системы (например, диалоговые окна открытия или сохранения файла) были потенциально опасны или не важны на определенных этапах, в общем, их не реализовали. Следовательно, и в ASwing нет того же окна выбора файла или шрифта. С другой стороны, реализована возможность создания собственных окон, комбинируя как простые элементы управления (такие, как текстовые поля, списки, кнопки), так и более сложные (таблица, дерево). Но первое, с чем нам надо разобраться – это собственно класс, отвечающий за создание окна. Знакомьтесь, JFrame. На вход конструктору можно подать параметры, управляющие тем, какой компонент будет считаться родительским для окна, собственно заголовок окна, и признак того, будет или нет это окно модальным. Затем мы должны начать наполнять окно содержимым и поведением. При создании окна оно уже имеет встроенные стандартные кнопки: свернуть, развернуть и закрыть. Если с первыми двумя вопросов не возникает, то для кнопки "закрыть окно" возможны варианты поведения. Что значит закрыть окно? Просто спрятать, так чтобы в будущем его можно было заново отобразить, или окно больше не нужно и его следует уничтожить, освободив ресурсы. Или как вариант окно вообще нельзя закрыть. Этими способами поведения управляет метод setDefaultCloseOperation, в качестве параметра которому передается одна из следующих констант:

1. DO_NOTHING_ON_CLOSE – ничего не происходит, окно не прячется и не уничтожается.
2. HIDE_ON_CLOSE – окно будет спрятано, но не уничтожено.
3. DISPOSE_ON_CLOSE – этот вариант выбран по-умолчанию, и означает, что окно будет сначала спрятано, а затем и уничтожено, ресурсы его должны быть освобождены. Однако при повторном вызове метода show окно может быть сконструировано заново, что должно занять большее количество времени. Ладно-ладно, на практике я не заметил особой разницы между HIDE_ON_CLOSE и DISPOSE_ON_CLOSE. Просто для себя я составил правило. Если окно просто прячется и его можно использовать повторно - HIDE_ON_CLOSE, иначе DISPOSE_ON_CLOSE.

Созданное окно располагается в пределах окна ролика. Ведь, по сути, окно – это просто очень большой клип. Возможно при создании окна указать его местоположение и размеры, для этого используйте свойства x, y и метод setSizeWH. Расположив окно, вы можете запретить перемещать его с помощью метода setDragable, получающего на вход булеву переменную – признак того, можно или нет "тягать" окно за его заголовок.

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

После создания формы и наполнения ее элементами управления неплохо было бы задать для формы именно такой размер, чтобы уместить все, что нужно, не более и не менее. Есть специальный метод pack, который выполняет упаковку элементов. Т.е. и форма и элементы занимают свои предпочтительные размеры.

Для многих элементов управления можно управлять тем, какой именно шрифт будет использован для его вывода. Так, у класса Component, лежащего в основе иерархии для каждого визуального компонента ASWing, есть пара методов getFont и setFont, служащих соответственно для получения информации о текущем шрифте, а также для его замены. Информация о шрифте задается с помощью объекта ASFont. Когда вы его создаете, необходимо указать в качестве параметров название шрифта, его размер, логические признаки, будет ли шрифт полужирным, курсивом, подчеркнутым. Так, в примере ниже я создаю набор надписей (JLabel) с различными размерами шрифта.

import flash.text.Font;
var allFonts:Array = Font.enumerateFonts(true);
// получаем список доступных в системе шрифтов
for (var i = 0; i <allFonts.length; i++){
var lab : JLabel = new JLabel (allFonts[i].fontName);
// создаем новый шрифт и тут же его назначаем объекту-надписи
lab.setFont (new ASFont (allFonts[i].fontName, 12) );
panel_top.append (lab);}

Что же, получилось неплохо. Но, рассматривая результаты работы, можно увидеть проблему. У нас слишком много информации при слишком небольшой доступной области экрана. Проще говоря, что делать, если некоторый компонент занимает слишком много места. Как нам добавить для него полосы прокрутки? Даже компонент JTextArea, созданный с явным количеством столбцов и строк, не содержит полос прокрутки по умолчанию, и когда содержимое не помещается, то просто обрезается по краям. Общая идея в том, чтобы компонент поместить внутрь специальной области прокрутки JScrollPane, например, так (хотя пример для textarea, но методика будет работать всегда, более того, есть ряд компонентов, которые не будут отображаться корректно, если их не поместить внутрь JScrollPane):

var area : JTextArea = new JTextArea ("Simple Text", 10, 10);
panel_top.append(new JScrollPane (area) );

После создания формы неплохо было бы добавить к ней меню. Меню создается с помощью класса JMenuBar. Меню состоит из множества наборов падающих пунктов. Отдельный пункт представляется экземпляром класса JMenuItem, а набор пунктов – с помощью Jmenu. Когда вы создаете JMenuItem, вы можете указать не только название пункта меню и иконку, но и комбинацию символов, которая может быть использована для активации данного пункта меню. Кроме "обычного" пункта меню существуют особые разновидности: JCheckBoxMenuItem – пункт меню с отметкой (checkbox), а также JRadioButtonMenuItem – (пункты меню такого вида объединяются в группу взаимно-исключающего выбора).

В примере ниже я создаю объект JMenuBar, в который помещается двух-уровневое падающее меню. Я совместил добавление конкретных пунктов меню с добавлением обработчиков событий. Также в примере показано, как создавать радио-кнопки. Т.к. из них только одна может быть выбрана, то необходимо создать специальную невизуальную группу "ButtonGroup", в которую и поместить радио-пункты меню.

var bar:JMenuBar = new JMenuBar();
// создаем меню
var specMenu = new JMenu("Special Ops");
// создаем первое под-меню
var editMenu:JMenu = new JMenu("Edit");
// создаем второе под-меню
specMenu.append(editMenu);
// создаем пункты меню и устанавливаем обработчики событий для них
var ite1 : JMenuItem = editMenu.addMenuItem("Copy");
ite1.setAccelerator(new KeySequence(KeyStroke.VK_CONTROL, KeyStroke.VK_C));
ite1.addActionListener(__onClickMenu_1);
editMenu.addMenuItem("Paste").addActionListener(__onClickMenu_2);
editMenu.addMenuItem("Cut").addActionListener(__onClickMenu_3);
// здесь я создаю специальный элемент-разделитель пунктов меню
editMenu.append(new JSeparator(JSeparator.HORIZONTAL));
// создаем объект иконки для назначения пункту меню
var ico2 : LoadIcon = new LoadIcon ("ico_fla9_2.PNG");
// в отличие от предыдущего подхода я создаю объект пункта меню явно и указываю и его название, и его иконку
var st : JMenuItem = new JMenuItem ("Stop", ico2);
editMenu.append(st);

// теперь я созданю две радиокнопки
var radio1:JRadioButtonMenuItem = new JRadioButtonMenuItem("Radio 1");
radio1.addSelectionListener(__onClickMenu_3);
var radio2:JRadioButtonMenuItem = new JRadioButtonMenuItem("Radio 2");
radio2.addSelectionListener(__onClickMenu_4);
var group:ButtonGroup = new ButtonGroup();
group.append(radio1);
group.append(radio2);
editMenu.append(radio1);
editMenu.append(radio2);
// добавляем подменю первого уровня в главное меню
bar.append(editMenu);
// теперь последний шаг – необходимо добавить созданное меню на форму
frame.getContentPane().append(bar, BorderLayout.NORTH);

Результаты работы скрипта показаны на рис 1. Последней строкой примера я добавил на форму созданное меню. Однако прием, которым я это сделал, несколько необычен и отличается от того, как в более ранних примерах я размещал элементы управления на каких либо контейнерах. Объект JFrame в отличие от JPanel не позволяет добавлять на себя элементы непосредственно. Вам необходимо получить ссылку на специальный объект ContentPane, и только после этого добавлять на него содержимое. Метод append также необычен: раньше я передавал в качестве его параметра только дочерний компонент, сейчас указано странное выражение "BorderLayout.NORTH". Давайте разберем его подробнее.

BorderLayout – это имя менеджера раскладки, который располагает элементы по правилу "север-юг-восток-запад-центр". Т.е. все пространство родительского элемента делится на пять зон, примерно так, как показано на рис. 2. Между областями существует расстояние, называемое hgap и vgap – для горизонтали и вертикали соответственно. Эти значения можно указать при вызове конструктора раскладки, как показано на примере ниже.

Panel p = new Panel();
p.setLayout(new BorderLayout(10,20));
p.add(new Button("Ok"), BorderLayout.SOUTH);

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

Откровенно говоря, это та из немногих вещей, которая мне никогда не нравилась в swing и aswing – сложность кардинальной переработки интерфейса. В мире java эта проблема решалась наличием разработанных third-party моделей раскладок, учитывающих самые хитрые задачи. Aswing же пока еще слишком молод, чтобы иметь набор подобных решений.

Раз уж мы рассмотрели обычное меню, то пора разобраться и с контекстным. Создается контекстное меню абсолютно идентично основному меню формы. Только называние класса должно быть не JMenuBar, а JPopupMenu. Затем вам необходимо по событию клика на некотором элементе управления вызвать метод show, указав как параметр родительский элемент для popup, а также координаты, где он будет показан. Единственный недостаток – в невозможности создания меню, отображаемого именно при нажатии на правую кнопку мыши (если вы знаете способ – сообщите мне об этом). И наконец, пример кода создающего кнопку меню, при нажатии на которую это меню и отображается:

frame = new JFrame(null, "Popup Menu Demo");
popupMenu = new JPopupMenu();
popupMenu.addMenuItem("Menu Item 1").addActionListener(__menuItemAction);
popupMenu.addMenuItem("Menu Item 2").addActionListener(__menuItemAction);
var button:JButton = new JButton("Show Popup Menu");
frame.getContentPane ().append (button);
button.addActionListener(__popupMenu);

function __popupMenu(e:Event):void {
var source:Component = e.target as Component;
popupMenu.show(source, source.getMousePosition().x, source.getMousePosition().y);}
// функция обработчик событий выбора пунктов меню
function __menuItemAction(e:Event):void {}

Теперь, научившись создавать форму и меню, пора перейти к специализированным элементам контейнерам. Самый простой из них – это JAttachPane. Это особый вид панели, который служит для отображения произвольного swf файла внутри себя. Кроме возможности указать, что за файл должен быть загружен, вы можете управлять тем, как именно будет выполняться масштабирование содержимого. В примере вызова конструктора первым параметром было указано имя внешнего swf-файла, второй параметр определяет стратегию расчета предпочитаемых размеров панели. Здесь возможны следующие варианты: PREFER_SIZE_BOTH – по возможности учитывать размеры внешнего файла и размеры, задаваемые менеджером раскладки, в которую помещена панель. Следующий вариант: PREFER_SIZE_IMAGE – размеры панели задаются только размерами внешнего файла. И третий вариант: PREFER_SIZE_LAYOUT – важны только интересы менеджера раскладки. Есть еще одна панель, умеющая отображать внешнюю информацию: JLoadPane. Ее назначение – загрузка файлов изображений, например, так:

loadPane = new JLoadPane("userphoto.gif", FloorPane.CENTER);
loadPane.setScaleMode(FloorPane.SCALE_FIT_PANE);

Более сложный пример – это контейнер JTabbedPane. Представляет собой набор закладок, на каждой из которых может быть размещено свое содержимое. После создания этого контейнера вам следует указать то, как именно будут расположены его закладки. Для этого укажите при вызове метода setTabPlacement одну из следующих констант: JTabbedPane.TOP, JTabbedPane.BOTTOM, JTabbedPane.LEFT, JTabbedPane.RIGHT
– закладки будут расположены вверху, внизу, слева и справа соответственно. Когда вы добавляете некоторый контейнер как собственно закладку, то вам следует указать не только название закладки, но и, возможно, задать значение иконки и некоторый текст – подсказку, появляющуюся при наведении мыши на заголовок закладки. Результат работы следующего скрипта приведен на рис. 3.
// создаем набор закладок
var tabbedPane = new JTabbedPane();
// указываем место расположения закладок
tabbedPane.setTabPlacement(JTabbedPane.TOP);
// создаем набор закладок
var pane1:JPanel = new JPanel();
// теперь создаем панель, содержащую все наполнение для конкретной закладки
pane1.append(…);
var ico2 : LoadIcon = new LoadIcon ("ico_fla9_2.PNG");
// и добавляем в контейнер созданную закладку
tabbedPane.appendTab(pane1, "Buttons", ico2, "Help Me");
Еще один интересный контейнер – это JAccordion. Представляет собой набор вертикально расположенных закладок в стиле ms outlook. Работа с ним практически идентичная работе с JTabbedPane. Только не следует указывать месторасположение закладок. А так, создаем панель, наполняем ее содержимым и добавляем на Accordion с помощью метода appendTab с такими же параметрами, как и в прошлый раз.
accordion = new JAccordion();
// создаем и наполняем содержимым панель
var pane1:JPanel = new JPanel();
accordion.appendTab(pane1, "Tab 1");
// и еще одна панель
var pane2:JPanel = new JPanel();
accordion.appendTab(pane2, "Tab 2");

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

JAdjuster - элемент управления, представляющий собой комбинацию текстового поля и ползунка для выбора значения в некотором диапазоне. Вы можете задать минимальное и максимальное значение диапазона, а также стиль расположения ползунка. Более интересна возможность задания пользовательского форматирования, выбранного с помощью ползунка значения. Так, в примере ниже я создаю специальную функцию, вызываемую при выборе некоторого значения ползунком, ее задача вернуть строку текста с отформатированным числом (так я добавил знак % после числа). Результаты работы показаны на рис. 4.

var adjuster = new JAdjuster(4);
adjuster.setMaximum(200);
adjuster.setMinimum(50);
adjuster.setOrientation(JAdjuster.VERTICAL);
adjuster.setValueTranslator(function(value:int):String{return Math.round(value) + "%";});

Следующий компонент - JProgressBar. При его создании, так же как и для Adjuster, следует указать стиль расположения: вертикальный или горизонтальный, а также отрезок возможных значений. Существуют две разновидности ProgressBar – детерминированный и недетерминированный. Изначально ProgressBar применялся для отображения степени выполнения некоторого процесса или работы, возможна ситуация, когда мы не знаем когда именно данная работа будет завершена. Вот здесь нам и пригодится недетерминированный ProgressBar. Его полоска просто бегает справа налево или снизу вверх – мол, смотри пользователь, работа все еще продолжается. Очевидно, что в последнем случае не имеет значения ни нижний, ни верхний диапазон значений параметра, ни его текущее значение.

var progBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
// устанавливаем текущее значение ProgressBar
progBar.setValue (70);
var indeterminateBar = new JProgressBar(JProgressBar.VERTICAL);
indeterminateBar.setIndeterminate(true);

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

black zorro black-zorro@tut.by


Компьютерная газета. Статья была опубликована в номере 26 за 2007 год в рубрике программирование

©1997-2024 Компьютерная газета