Использование Wizard API

В этой статье речь пойдет о таких сложных компонентах пользовательского интерфейса, как визарды (wizards, от англ. — "специалист своего дела", "волшебник"). Многие из вас почти наверняка сталкиваются с ними чуть ли не каждый день, а то и много раз в день. При этом не может не радовать тот факт, что сейчас мы без особых усилий можем использовать эти компоненты в наших Java-приожениях. Визарды помогают решать ряд общих задач — мы используем их, например, для настройки сетевых соединений и копирования фотографий с цифровой камеры на компьютер. Они позволяют значительно упростить процесс, который состоит из нескольких шагов, помогая пользователю последовательно пройти каждый из них. В первой части мы рассмотрим, что собой представляют визарды, как они работают, и почему их стоит использовать с своих приложениях. Далее мы познакомимся с проектом сайта java.net, который предлагает очень простую в использовании реализацию визард-компонентов. И, естественно, научимся пользоваться средствами, которые эта библиотека нам предоставляет.

Что такое визарды?


Чтобы понять, как работают визарды, рассмотрим процесс создания нового сетевого подключения в ОС Windows XP на примере создания интернет- подключения. Этот процесс занимает несколько шагов. Последовательность каждого последующего шага может зависеть от тех данных и опций, которые пользователь выбрал в предыдущих диалоговых окнах. Например:

1. Вывод общей информации о самом Мастере новых подключений.
2. Выбор типа сетевого подключения (Подключить к Интернету).
3. Выбор способа подключения к Интернету (Установить подключение вручную).
4. Выбор оборудования для подключения (Через обычный модем).
5. и т.д.


Рис. 1. Визард Мастер новых подключений в Windows XP

Для того чтобы перемещаться от одного окна к другому, используются кнопки Далее (Next) и Назад (Previous). Если нам необходимо прервать выполнение визарда, мы можем нажать кнопку Отмена (Cancel). На конечном шаге визарда для завершения процесса мы нажимаем кнопку Готово (Finish). На этом окончательном этапе вся необходимая информация была собрана, и визард может завершить свою работу, выполнив необходимые действия.


Рис. 2. Заключительный шаг визарда Мастер новых подключений

Среда разработки Java-приложений NetBeans использует визарды для многих целей. Например, для того, чтобы провести вас через процесс создания нового проекта. Сюда входят следующие шаги:

1. Выбор типа проекта.
2. Выбор его имени и местоположения на диске.

Если не брать во внимание некоторые различия во внешнем виде визарда Windows XP для создания сетевых подключений и визарда NetBeans для создания проекта, в целом они очень похожи друг на друга. Так что же такое визард? С точки зрения пользователя визард помогает выполнять сложные задачи, разбивая их на отдельные логические куски. Эти куски могут быть панелями или диалогами, которые отображают какую-либо справочную информацию и/или ожидают от пользователя ввода данных. Пользователь может переходить от одного шага к другому как в прямом, так и в обратном порядке, корректируя, если необходимо, ранее введенные данные. Когда пользователь пройдет по всей цепочке шагов, он попадает на диалог с возможностью завершить работу визарда. Обычно крайним диалогом принято считать информационный, в котором приводится общая информация о собранных данных и о том, что будет выполнено в результате (как, например, на рис. 2). Это означает, что, пока пользователь не нажмет кнопку Готово (Finish), не должно выполняться никаких операций, кроме сбора данных. Такой метод разбиения сложных задач на более мелкие логические части уже давно получил широкое признание среди пользователей и применяется в большинстве приложений. Основная причина — это то, что визарды просты и понятны даже для неопытных пользователей.

Проект Wizard на java.net

Проект Wizard ( сайт ) является частью более крупного проекта SwingLabs ( сайт ), который дает возможность экспериментировать с различными расширениями существующих Swing-компонент, а также предоставляет некоторые абсолютно новые компоненты. Вполне вероятно, что некоторые компоненты из этого набора могут быть включены в список стандартных Swing-компонент в будущих версиях JDK (Java Development Kit). Проект Wizard предоставляет библиотеку классов, которая реализует простой API для создания собственных визардов. Цель проекта — предоставить простое в использовании решение, которое позволяет создавать визарды в Swing-приложениях с написанием минимального количества кода с минимальными усилиями. Чтобы приступить к использованию библиотеки Wizard, вы можете либо скачать последние исходники из CVS репозитория и скомпилировать их самостоятельно, либо скачать готовый jar-файл и документацию прямо с домашней страницы проекта. Чтобы использовать полученный jar-файл, не забудьте включить его в переменную CLASSPATH вашего приложения или скопировать в директорию расширений вашей виртуальной Java-машины.

Простой пример

После краткой теории приступим к реализации простого примера, в котором мы с помощью библиотеки Wizard сделаем простой визард с одним диалоговым окном:

Листинг 1.
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;

import org.netbeans.api.wizard.WizardDisplayer;
import org.netbeans.spi.wizard.Wizard;
import org.netbeans.spi.wizard.WizardController;
import org.netbeans.spi.wizard.WizardException;
import org.netbeans.spi.wizard.WizardPanelProvider;

public class WizardPanelProviderDemo extends WizardPanelProvider {

private static final String ID = "page1-id";

public WizardPanelProviderDemo() {
super("Пример визарда", new String []{ID}, new String []{"Шаг №1"});
}

protected JComponent createPanel(final WizardController wizardController, String str, final Map map) {

if (str.equals(ID)) {

JPanel mainPanel = new JPanel( new FlowLayout(FlowLayout.LEADING));

final JCheckBox chkSelectMe = new JCheckBox("Выберите, чтобы продолжить", false);

ActionListener chkListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {

boolean isSelected = chkSelectMe.isSelected();

wizardController.setProblem(isSelected ? null : "Вы должны выбрать checkbox, чтобы продолжить");

map.put("boxSelected", isSelected ? Boolean.TRUE : Boolean.FALSE);
}
};

chkSelectMe.addActionListener(chkListener);

chkListener.actionPerformed(new ActionEvent(chkSelectMe, 0, chkSelectMe.getActionCommand()));

mainPanel.add(chkSelectMe);

return mainPanel;
}

return null;
}

protected Object finish(Map settings) throws WizardException {
Set keys = settings.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println(key + "=" + settings.get(key));
}
return settings;
}

public static void main(String [] args) {
WizardPanelProvider provider = new WizardPanelProviderDemo();
Wizard wizard = provider.createWizard();
Object result = WizardDisplayer.showWizard(wizard);
}
}

В результате работы нашего примера вы должны получить следующее диалоговое окно визарда:


Рис. 3. Результат примера, приведенного в листинге 1

Создание и отображение визарда, изображенного на рис. 3, включает три этапа:
1. Подготовка визарда. Наследуется класс org.netbeans.spi.wizard.WizardPanelProvider.
2. Создание объекта класса org.netbeans.spi.wizard.Wizard.
3. Вывод визарда на экран с помощью класса org.netbeans.api.wizard.WizardDisplayer.
WizardPanelProvider — это идеальный базовый класс для создания визардов с фиксированным количеством панелей (шагов). Каждая из его панелей содержит локализованное описание для пользователя и уникальную строку-идентификатор. Описания и идентификаторы передаются конструктору класса WizardPanelProvider. Все, что он вас требуется — унаследовать этот класс и реализовать метод createPanel(), который бы создавал и возвращал экземпляр класса панели по полученному идентификатору. Наш класс WizardPanelProviderDemo состоит всего из одной панели, поэтому в нем нет необходимости проверять этот параметр, однако более сложные визарды должны это делать. На втором этапе мы создаем визард. Для этого достаточно вызвать метод createWizard() вашего класса, который унаследован от WizardPanelProvider. В нашем случае это класс WizardPanelProviderDemo. После этого визард можно отобразить, вызвав метод WizardDisplayer.showWizard(). Теперь, когда мы уже познакомились с основными моментами создания визардов, можно приступить к рассмотрению некоторых важных идей, лежащих в основе Wizard API.

Основные моменты архитектуры Wizard API

Основная идея визарда в сборе данных, введенных пользователем, и использовании этих данных во время нажатия кнопки Готово (Finish) для выполнения требуемых операций. Wizard API использует класс java.util.Map для хранения этих данных. Объект с данными передается методу finish() после того, как визард успешно завершает свое выполнение. Метод finish() принадлежит классу WizardPanelProvider и дожен быть перегружен в вашем производном классе в любом случае. Этот метод должен возвращать объект, который визард создаст на основе собранных данных, либо это может быть все тот же объект класса Map. Реализация этого метода по умолчанию возвращает null. Обычно обновление данных в этом объекте происходит в момент изменения данных на панелях визарда. Чтобы это осуществить, вы можете добавлять соответствующие обработчики событий компонент на панелях. Например, в приведенном выше примере мы добавили обработчик класса ActionListener к компоненту JCheckBox. В методе actionPerformed() этого обработчика мы вносим необходимые изменения в Map-объект, который был передан в метод createPanel() и доступен для внесения в него необходимых изменений. Как мы дальше убедимся, Wizard API может помочь нам с автоматизацией этой процедуры. Еще одним важным моментом является осуществление контроля навигации между панелями визарда. Если нужно предотвратить возможность перехода пользователем на следующую панель при невыполнении какого-либо условия, вы можете вызвать метод setProblem() у объекта класса WizardController, который был передан методу createPanel(). Если методу setProblem() передать null, это будет означать, что никаких проблем нет, и навигация будет разблокирована. В другом случае информационное сообщение о том, что что-то не так, будет выведено в нижней части визарда. Однако следует очень внимательно и осторожно относиться к блокировке навигации, поскольку блокировать кнопки имеет смысл только в случае, когда некоторые данные имеют существенное значение для успешного завершения визарда.

Использование WizardPage

Наш первый пример, приведенный выше в Листинге 1, наследовал класс WizardPanelProvider для создания визарда. Другой вариант — использование класса org.netbeans.spi.wizard.WizardPage. Этот класс также очень прост в применении. Его основное достоинство в том, что он может заполнять объект Map-класса для хранения установок и данных автоматически. Чтобы увидеть, как он работает, обратите внимание на следующий листинг класса WizardPageDemo.

Листинг 2.
import java.awt.FlowLayout;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.JCheckBox;

import org.netbeans.api.wizard.WizardDisplayer;
import org.netbeans.spi.wizard.Wizard;
import org.netbeans.spi.wizard.WizardException;
import org.netbeans.spi.wizard.WizardPage;
import org.netbeans.spi.wizard.WizardPage.WizardResultProducer;

public class WizardPageDemo implements WizardResultProducer {

public WizardPageDemo() {
WizardPage page1 = new WizardPage("page1-id",
"Шаг №1");
JCheckBox cb = new JCheckBox("Выберите меня", false);
cb.setName("checkbox-1");
page1.setLayout(new FlowLayout(FlowLayout.LEADING));
page1.add(cb);
Wizard wizard = WizardPage.createWizard(new WizardPage []{page1}, this);
Object result = WizardDisplayer.showWizard(wizard);
}

public static void main(String [] args) {
new WizardPageDemo();
}

/*
* WizardResultProducer interface
*/
public Object finish(Map settings)
throws WizardException {
Set keys = settings.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println(key + "=" + settings.get(key));
}
return settings;
}
}

В приведенном в листинге 2 примере создание визарда состоит из следующих шагов:

1. Создается экземпляр класса WizardPage и добавляются необходимые компоненты.
2. Вызывается метод setName() для каждого из компонентов, которые должны появиться в объекте Map-класса.
3. Создание визарда с помощью метода WizardPage.createWizard().

Метод WizardPage.createWizard() принимает в качестве параметра массив объектов класса WizardPage. Для того чтобы знать, когда визард успешно завершит свою работу, вы можете реализовать интерфейс WizardResultProducer и передать методу createWizard() экземпляр вашего класса как дополнительный аргумент. Классы WizardPage и WizardPanelProvider очень хорошо подходят для использования в тех случаях, когда визард состоит из фиксированного числа панелей, порядок которых определен заранее. Однако этот метод не подходит, когда порядок панелей меняется в зависимости от введенных пользователем данных, как, например, в случае с рассмотренным в самом начале визардом Мастер новых подключений из Windows XP. Однако Wizard API справляется и с такой задачей.

Ответвления

Класс WizardBranchController используется для того, чтобы динамически изменять порядок панелей в визарде. Этот класс абстрактный, поэтому вам необходимо его унаследовать и расширить необходимыми методами. Конструктор этого класса принимает один аргумент, который предоставляет начальный набор панелей визарда. После того, как крайняя панель из этого списка достигнута, класс WizardBranchController будет опрошен на предмет предоставления следующего набора панелей. Чтобы определить, какие или какую панель выдавать дальше, ваш наследник класса WizardBranchController должен проверить данные, которые находятся на данный момент в Map-объекте с сохраняемыми данными. Естественно, чтобы логическая цепочка не прерывалась, предыдущий набор панелей должен каким-то образом этот Map-объект изменить, чтобы дать понять, что произошло, и как следует действовать дальше. Вот пример метода класса, который наследует WizardBranchController:

protected WizardPanelProvider getPanelProviderForStep(String step, Map settings) {
String str;
str = (String)settings.get("combobox");
if (str.equals("red")) {
return wppRed;
} else if (str.equals("yellow")) {
return wppYellow;
} else {
return wppGreen;
}
}

Ваш наследник класса WizardBranchController должен перегружать один из двух методов: либо getWizardForStep(), либо getPanelProviderForStep(). Какой из них вы выберете, зависит от того, как вы планируете создавать ваши последующие панели. Здесь мы перегружаем метод
getPanelProviderForStep(), поскольку в этом случае мы можем повторно использовать класс, который реализует первый набор панелей. По умолчанию реализация getWizardForStep() вызывает метод getPanelProviderForStep() и возвращает результат этого вызова.

Резюме

Wizard API — это действительно очень простое в использовании расширение основных классов Swing. Вследствие того, что визарды сегодня крайне популярны и используются повсеместно, было бы правильно включить этот API в состав будущего релиза Java-платформы. Хотя Wizard API на данный момент и находится в разработке, он уже сейчас является достаточно стабильным проектом. Согласно заявлениям на домашней странице проекта, в скором будущем планируется добавить итоговую панель для вывода статистики о проделанных работах после нажатия кнопки Готово (Finish) и, возможно, сменить имя пакета на net.java (поскольку сейчас проект использует часть исходного кода NetBeans).

Ресурсы

. Исходный код по этой статье: сайт
. Проект Wizard: сайт
. Wizard API JAR: сайт
. Wizard API документация: сайт

По материалам Thomas Kunneth

Алексей Литвинюк, litvinuke@tut.by


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

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