Статьи

Добавление проверки входных данных SWT простым способом

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

При создании приложения Eclipse RCP есть некоторая помощь, предоставляемая SWT и JFace. Мы можем добавить ModifyListener s и VerifyListener s к определенным виджетам SWT. JFace также предоставляет ControlDecoration s, чтобы помочь нам указать пользователю, где существует проблема с определенным входным значением.

Проблема заключается в том, что они находятся на низком уровне, и нам нужно сделать много «обезьяньего» кодирования, просто чтобы добавить базовую проверку и индикацию ошибок в виджет, и тогда мы даже не касаемся мира масок ввода. Если вы похожи на меня, вы хотите сконцентрироваться на решении вашей бизнес-задачи и не хотите писать много базового кода интерфейса снова и снова.

Вот где RCP Toolbox очень полезен. Он предоставляет облегченную среду проверки (среди прочих функций), которая значительно упрощает добавление проверочных масок и масок ввода в виджеты SWT Text , Combo и CCombo .

Цель

Давайте посмотрим, как определить базовый мастер для создания нового бронирования. Этот мастер должен захватить следующие поля от пользователя:

  • Имя : имя бронирования, обычно имя человека, делающего бронирование. Может быть не пустым и желательно не менее трех символов.
  • Дата : дата и время бронирования. Должно быть в любое время с текущего времени до конца года.
  • Количество человек : количество человек, на которых можно забронировать. Должно быть число от 1 до 10.
  • Номер телефона : номер телефона лица, осуществляющего бронирование. Должен быть в форме + (код страны) (код города) , например, +44 (33) 555-1111.

     

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

    Валидационные рамки

    RCP Toolbox предоставляет несколько пользовательских виджетов и простой в рамках проверки использования. Добавление проверки начинается с класса ValidationToolkit . Этот класс создается для работы с определенным типом содержимого, а затем используется для создания экземпляров ValidatingField, которые могут обрабатывать этот тип содержимого.

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

    WizardPage и ValidationToolkits

    Сначала мы определим наш класс BookingWizardPage и создадим необходимые экземпляры ValidationToolkit .

    //Not all imports are shown
    import com.richclientgui.toolbox.validation.IFieldErrorMessageHandler;
    import com.richclientgui.toolbox.validation.ValidationToolkit;
    import com.richclientgui.toolbox.validation.converter.DateStringConverter;
    import com.richclientgui.toolbox.validation.converter.IntegerStringConverter;
    import com.richclientgui.toolbox.validation.string.StringValidationToolkit;

    public class BookingWizardPage extends WizardPage {
    private static final int DECORATOR_POSITION = SWT.TOP | SWT.LEFT;
    private static final int DECORATOR_MARGIN_WIDTH = 1;
    private static final int DEFAULT_WIDTH_HINT = 150;

    private StringValidationToolkit strValToolkit = null;
    private ValidationToolkit<Date> dateValToolkit = null;
    private ValidationToolkit<Integer> intValToolkit = null;

    private final IFieldErrorMessageHandler errorMessageHandler;

    public BookingWizardPage() {
    super("booking.pageone","New Booking Entry", null);
    errorMessageHandler = new WizardPageErrorHandler();
    }

    public void createControl(Composite parent) {
    final Composite composite = new Composite(parent, SWT.NONE);
    composite.setLayout(new GridLayout(2, false));

    strValToolkit = new StringValidationToolkit(DECORATOR_POSITION,
    DECORATOR_MARGIN_WIDTH, true);
    strValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    intValToolkit = new ValidationToolkit<Integer>(new IntegerStringConverter(),
    DECORATOR_POSITION, DECORATOR_MARGIN_WIDTH, true);
    intValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    dateValToolkit = new ValidationToolkit<Date>(new DateStringConverter(),
    DECORATOR_POSITION, DECORATOR_MARGIN_WIDTH, true);
    dateValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    //TODO: create ValidatingFields
    setControl(composite);
    }
    }

    Класс StringValidationToolkit, который мы создаем в строке 28, является ValidationToolkit, который имеет дело именно с ValidatingField, которые имеют нормальное содержимое String . В строке 32 мы создаем экземпляр типизированного экземпляра ValidationToolkit , который создаст ValidatingField, который принимает в качестве входных данных только целые числа .

    Мы должны обеспечить способ преобразования содержимого полей из строки в правильный тип содержимого. Это делается с помощью набора классов покрытия, предоставляемых фреймворком. В строках 32 и 36 мы указываем IntegerStringConverter и DateStringConverter для преобразования значений Integer и java.util.Date соответственно.

    Фреймворк использует JFace org.eclipse.jface.fieldassist.ControlDecoration и связанные с ним классы, чтобы указать, есть ли у поля ошибка или условие предупреждения, является ли оно обязательным полем и имеется ли быстрое исправление (по праву -нажмите на иконку декоратора) для текущей ошибки или состояния предупреждения. Положение этих значков декоратора относительно входных виджетов, а также ширина поля между значком декоратора и виджетом могут быть указаны при создании ValidationToolkit . Все поля, созданные этим экземпляром ValidationToolkit, будут использовать одинаковые настройки для значков декоратора. В строках 9 — 10мы определили некоторые константы для позиции декоратора и полей, и мы используем это для построения всех экземпляров ValidationToolkit .

    Обработка сообщений об ошибках

    Мы также используем IFieldErrorMessageHandler, чтобы получить обратную связь от процесса проверки. Среда проверки будет вызывать эти обработчики ошибок при возникновении ошибок или предупреждений и позволять нам что-то делать с этими сообщениями. По умолчанию эти сообщения отображаются только во всплывающих подсказках значков декоратора. Для каждого экземпляра набора инструментов может быть указан обработчик ошибок по умолчанию, или, если требуется, для каждого ValidatingField может быть установлен отдельный обработчик .

    Внутренний класс WizardPageErrorHandler реализует интерфейс IFieldErrorMessageHandler и в основном просто устанавливает сообщения в области сообщений WizardPage .

    	//inner class of BookingWizardPage
    class WizardPageErrorHandler implements IFieldErrorMessageHandler {

    public void handleErrorMessage(String message, String input) {
    setMessage(null, DialogPage.WARNING);
    setErrorMessage(message);
    }

    public void handleWarningMessage(String message, String input) {
    setErrorMessage(null);
    setMessage(message, DialogPage.WARNING);
    }

    public void clearMessage() {
    setErrorMessage(null);
    setMessage(null, DialogPage.WARNING);
    }
    }

    Фактические сообщения об ошибках или предупреждения генерируются различными реализациями IFieldValidator (мы вернемся к ним) и могут быть легко настроены путем реализации пользовательских валидаторов.

    Создание простого ValidatingField

    Конечно, наличие некоторых экземпляров инструментария не очень нам помогает. Нам нужны фактические входные виджеты, которые проверяются. Первым шагом является обновление метода createControl .

    	public void createControl(Composite parent) {
    final Composite composite = new Composite(parent, SWT.NONE);
    composite.setLayout(new GridLayout(2, false));

    strValToolkit = new StringValidationToolkit(DECORATOR_POSITION,
    DECORATOR_MARGIN_WIDTH, true);
    strValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    intValToolkit = new ValidationToolkit<Integer>(new IntegerStringConverter(),
    DECORATOR_POSITION, DECORATOR_MARGIN_WIDTH, true);
    intValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    dateValToolkit = new ValidationToolkit<Date>(new DateStringConverter(),
    DECORATOR_POSITION, DECORATOR_MARGIN_WIDTH, true);
    dateValToolkit.setDefaultErrorMessageHandler(errorMessageHandler);

    createNameField(composite);
    createDateField(composite);
    createNumberPersonsField(composite);
    createTelephoneNumberField(composite);

    setControl(composite);
    }

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

    	private void createNameField(Composite composite) {
    new Label(composite, SWT.NONE).setText("Booking Name:");

    final ValidatingField<String> nameField = strValToolkit.createTextField(
    composite, new IFieldValidator<String>(){

    public String getErrorMessage() {
    return "Name may not be empty.";
    }

    public String getWarningMessage() {
    return "That's a very short name...";
    }

    public boolean isValid(String contents) {
    return !(contents.length()==0);
    }

    public boolean warningExist(String contents) {
    return contents.length() < 3;
    }

    }, true, "");
    GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
    gd.widthHint = DEFAULT_WIDTH_HINT;
    nameField.getControl().setLayoutData(gd);
    }

    Поскольку это поле работает с содержимым String , мы используем экземпляр strValToolkit для создания поля в строке 4 выше. Мы указываем родительский композит, к которому должен быть добавлен входной виджет, IFieldValidator, который будет использоваться для проверки содержимого поля, независимо от того, является ли это поле обязательным или нет (таким образом, должен ли отображаться требуемый значок декоратора) и начальный пустое строковое значение для поля. Обратите внимание, что этот вызов также создаст текстовый виджет, который будет использоваться для поля, но API позволяет вам создавать свой собственный экземпляр Text , Combo или CCombo и передавать его в инструментарий для использования при создании новогоValidatingField .

    Анонимная реализация внутреннего класса IFieldValidator указана в строках 5 — 23 . В этом примере мы проводим некоторые базовые проверки валидации, но легко внедрить валидаторы, использующие другие мощные бизнес-среды валидации. Наш валидатор укажет состояние ошибки, если содержимое поля пустое ( строка 16 ), и в этом случае сообщение об ошибке «Имя не может быть пустым». будет отображаться ( строка 8 ). Этот валидатор также укажет условие предупреждения, если поле имени содержит менее 3 символов ( строка 20 ) с сообщением «Это очень короткое имя …» ( строка 12 ).

    В строках 24 — 26 мы устанавливаем расположение входного виджета на композите.

    Знакомства маска ввода

    Даты всегда были сложным типом ввода, чтобы иметь дело с. Для нас, разработчиков, самый простой способ справиться с ними — использовать виджеты, которые открывают календарь, из которого пользователь может выбрать день и, возможно, время. Тем не менее, этот способ работы с датами не всегда является любимым у конечных пользователей с сенсорным набором. Они предпочитают какое-то замаскированное поле, в котором им нужно только заполнить биты даты, которые имеют значение. Использование мыши должно быть максимально ограничено. К счастью, RCP Toolbox предоставляет способ задания масок ввода, а также конкретные реализации валидаторов и преобразователей для работы с датами.

    Мы хотим создать поле, которое принимает в качестве входных данных только даты, где введенная дата должна иметь форму гггг-мм-дд ЧЧ: мм (например, 2008-07-19 21:00) и находиться в диапазоне дат, начинающемся с текущее время и окончание в конце 2008 года.

    	private void createDateField(Composite composite) {
    new Label(composite, SWT.NONE).setText("Booking Time:");

    final Date endYear = getEndYearDate();
    //we create a Date field that takes input of form yyyy-MM-dd HH:mm
    //and only allows values from now till the end of the year
    final ValidatingField<Date> rangedDateField
    = dateValToolkit.createTextField(composite,
    new RangedDateFieldValidator(
    DateFieldValidator.DATE_TIME_HHMM_DASH,
    dateValToolkit.getStringConverter(),
    new Date(), endYear),
    true,
    new Date());
    GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
    gd.widthHint = DEFAULT_WIDTH_HINT;
    rangedDateField.getControl().setLayoutData(gd);
    }

    Здесь мы использовали экземпляр dateValToolkit для создания поля, так как содержимое поля должно быть java.util.Date . Мы указываем экземпляр RangedDateFieldValidator (предоставляемый платформой), который использует указанный шаблон маски ввода Date DateFieldValidator.DATE_TIME_HHMM_DASH ( строка 9 ) для проверки содержимого поля. Доступны другие шаблоны или могут быть определены пользовательские. DateStringConverter указанной при dateValToolkit была построена будет использоваться для преобразования дат в строки и наоборот.

    In line 11 we specify the valid date range for the field, from the current date and time to the end of the year, and in line 13 we set the initial value of the field to the current time.

    Providing quick-fixes

    I found that developers using Eclipse RCP to develop their applications like to make the experience for the end-user as good as possible. So we should not stop at just validating input; we must also try and help them quickly fix mistakes, where possible. In this example we can do that by specifying a IQuickFixProvider.

    		//add at end of createDateField(..) method
    //we add a quickfix that will set it to the current date
    rangedDateField.setQuickFixProvider(new IQuickFixProvider<Date>(){

    public boolean doQuickFix(ValidatingField<Date> field) {
    field.setContents(new Date());
    return true;
    }

    public String getQuickFixMenuText() {
    return "Set to current time";
    }

    public boolean hasQuickFix(Date contents) {
    //would typically first check contents to determine if quickfix
    //is possible
    return true;
    }
    });

    The above is a very simple quick-fixer. It always says it has a quick-fix available (line 17), where a more complex provider will first check the contents of the field to determine if there is a quick-fix. When the user performs the quick-fix, it just sets the contents of the field to the current date and time (line 6).

    When the validation framework detects there is an error condition on a field, it will see if there is a IQuickFixProvider available with a quick-fix. If this is the case, it will add the quick-fix option to a context-menu on the decorator icon with the text specified in line 11. All the user then needs to do is right-click on the decorator icon and select the quick-fix to perform.

    Validating Combos

    A Combo widget would be just the thing to use for capturing the number of persons for the booking. Our requirements say we must limit the number to a maximum of 10 people (and a minimum of 1 goes without saying). Once again we do not want to force the user to use numerous mouse-clicks or keystrokes to select the number, so we do not make the Combo read-only, and rather decide to add validation to it.

    	private void createNumberPersonsField(Composite composite) {
    new Label(composite, SWT.NONE).setText("Number of persons:");

    final ValidatingField<Integer> numberPersonsField
    = intValToolkit.createComboField(
    composite, new StrictRangedNumberFieldValidator<Integer>(1, 10){
    public String getErrorMessage() {
    return "Bookings for groups bigger than 10 not allowed";
    }

    public String getWarningMessage() {
    return null;
    }

    public boolean warningExist(Integer contents) {
    return false;
    }

    },
    true,
    2,
    new Integer[]{1,2,3,4,5,6,7,8,9,10});
    GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
    gd.widthHint = DEFAULT_WIDTH_HINT;
    numberPersonsField.getControl().setLayoutData(gd);
    }

    Here we make use of the intValToolkit.createComboField method to create a field containing a Combo widget with contents of type Integer. We specify a StrictRangedNumberFieldValidator (line 6) to ensure that the entered value only consists of digits and falls in the range 1 to 10. No warning conditions are checked. In line 22 we populate the Combo with a list of Integers from 1 to 10, and in line 21 we select a default value of 2. As easy as counting to 5.

    And don’t forget the telephone number

    Freeform telephone number fields are used a lot, but unfortunately end-users can easily make mistakes in such fields. We want to force our user to input the telephone number in a specific form, thus at least preventing the cases where digits are missed. To do this, we make use of the framework’s TelephoneNumberValidator. This validator allows telephone number to be entered in either international format (e.g. +44 (55) 555-5555) or in domestic format (e.g. (055) 555-5555).

    	private void createTelephoneNumberField(Composite composite) {
    new Label(composite, SWT.NONE).setText("Contact Telephone Nr:");

    final ValidatingField<String> telephoneField
    = strValToolkit.createTextField(
    composite,
    new TelephoneNumberValidator(true),
    true,
    "+44 (55) 555-4321");

    GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false);
    gd.widthHint = DEFAULT_WIDTH_HINT;
    telephoneField.getControl().setLayoutData(gd);
    }

    We are using strValToolkit to create the field, since the contents will be managed as a String. Then we specify a TelephoneNumberValidator as the validator in line 7, with the true parameter indicating that we want to use the international format. In line 9 we provide an initial value.

    Conclusion

    This article describes a very simple example of how to add validation to SWT widgets using the RCP Toolbox. In a real-world application the actual business validations to be done might be more complex, but if this validation framework is used, the UI code related to validation would remain as simple as the code in these examples.

    This validation framework really made our development much easier, allowing us to concentrate on the business code. It is very easy to extend the framework with custom validators, converters, quick-fix providers and error handlers that ties into existing business code or other validation code, rules engines etc.

    Note that the examples in this article need Eclipse RCP 3.3 or 3.4 as well as RCP Toolbox v1.0.1, created and distributed by www.richclientgui.com.