Шаг 22.
Библиотека OWL.
Контроль данных диалогового окна

    На этом шаге мы рассмотрим организацию контроля за вводимыми данными.

    Области редактирования диалогового окна могут содержать текстовые данные любого типа. Например, если вы ожидаете, что пользователь введет свой телефонный номер в области ввода "Телефон", вы не можете быть уверены, что пользователь не введет $%-&&*&*& и не оставит окно диалога с этими данными. Очевидно, классы диалогового окна нуждаются в каком-то способе контроля введенных пользователем данных перед тем, как разрешить пользователю закрыть диалоговое окно.

    ObjectWindows предлагает несколько классов-контролеров ввода, которые вы можете связать с управляющими элементами диалогового окна. Эти классы используют TValidator в качестве базового класса. TValidator является абстрактным классом, который определяет основные принципы поведения контролера действительности данных (валидатора), и который вы можете использовать в качестве базового класса для своих собственных процедур проверки данных.

    Готовыми к использованию классами валидаторов в OWL являются:

    Еще один абстрактный класс-валидатор - TLookupValidator, на котором базируется TStringLookupValidator, представляет еще одну отправную точку для разработки ваших собственных процедур проверки по справочной таблице.

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

#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dialog.h>
#include <owl\dc.h>
#include <owl\edit.h>
#include <owl\validate.h>
#include "pr22_1.rc"

// Структура буфера обмена.
struct TTransBuf
{
  char nameEdit[81];
  char addrEdit[81];
  char cityEdit[81];
  char stateEdit[3];
  char zipEdit[11];
  char phoneEdit[20];
  char birthEdit[9];
};

// Буфер обмена.
TTransBuf transBuf;


//Класс приложения
class TApp: public TApplication
{
  public:
	 TApp():TApplication(){}
	 void InitMainWindow();
};

//Класс основного окна
class TWndw:public TFrameWindow
{
  public:
	 TWndw(TWindow *parent, const char far *title);
  protected:
	 void CmTestDialog();
	 void Paint(TDC &paintDC,BOOL,TRect&);

	 DECLARE_RESPONSE_TABLE (TWndw);
};

DEFINE_RESPONSE_TABLE1 (TWndw, TFrameWindow)
  EV_COMMAND(CM_TESTDIALOG,CmTestDialog),
END_RESPONSE_TABLE;

//Класс диалогового окна
class TDlg:public TDialog
{
  protected:
	  TTransBuf oldTransBuf;
  public:
	  TDlg(TWindow *parent, TResId resld);
  protected:
	  void SetupWindow();
	  BOOL CanClose();
	  void CmCancel();

	  DECLARE_RESPONSE_TABLE(TDlg);
};

DEFINE_RESPONSE_TABLE1(TDlg, TDialog)
  EV_COMMAND(IDCANCEL, CmCancel),
END_RESPONSE_TABLE;


TWndw::TWndw(TWindow *parent, const char far *title):
		  TFrameWindow(parent,title)
{
	// Добавить меню к главному окну.
	AssignMenu("MENU_1");
	// Определить расположение и размеры окна.
	Attr.X=50;
	Attr.Y=50;
	Attr.W=GetSystemMetrics(SM_CXSCREEN)/3;
	Attr.H=GetSystemMetrics(SM_CXSCREEN)/4;
	// Инициализировать буфер обмена.
	memset (&transBuf, 0, sizeof(transBuf));
}

// TWndw::CmTestDialog()
// Эта функция реагирует на команду Dialog/Test
// меню Dialog.
void TWndw::CmTestDialog()
{
  // Создать окно диалога.
  TDialog *dialog = new TDlg(this, ADDRDLG);
  // Выполнить окно диалога.
  int result = dialog->Execute();
  // Если пользователь нажал кнопку ОК... 
  if (result == IDOK)
     // Заставить главное окно перерисоваться
     // с новыми данными диалогового окна. 
     Invalidate();
}

//TWndw::Paint()
// Эта функция переопределяет функцию
// TWindow Paint(), реагирует на сообщение WM_PAINT,
// которое Windows посылает окну в случае, когда
// окно должно перерисоваться.
void TWndw::Paint(TDC &paintDC,BOOL,TRect&)
{
  TEXTMETRIC tm;
  //Получить текущую текстовую информацию.
  paintDC.GetTextMetrics(tm);
  //Установить выравнивание вправо выводимого текста.
  paintDC.SetTextAlign(TA_RIGHT);
  //Определить размер самой длинной метки
  TSize size=paintDC.GetTextExtent("BIRTHDAY:",9);
  //Вывести все метки в рабочую область окна
  paintDC.TextOut(size.cx+12,tm.tmHeight,"NAME:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*2,"ADDRESS:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*3,"CITY:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*4,"STATE:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*5,"ZIP:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*6,"PHONE:");
  paintDC.TextOut(size.cx+12,tm.tmHeight*7,"BIRTHDAY:");
  //Установить выравнивание влево выводимого на печать текста.
  paintDC.SetTextAlign(TA_LEFT);
  //Напечатать даннные диалогового окна в
  //соответствующих позициях на экране,
  //основываясь на длине самой длинной метки и высоте текущего шрифта.
  paintDC.TextOut(size.cx+20,tm.tmHeight,transBuf.nameEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*2,transBuf.addrEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*3,transBuf.cityEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*4,transBuf.stateEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*5,transBuf.zipEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*6,transBuf.phoneEdit);
  paintDC.TextOut(size.cx+20,tm.tmHeight*7,transBuf.birthEdit);
}

// ************************
// Реализация класса TDlg.
// ************************
// TDlg::TDlg()
// Конструктор диалогового окна.
TDlg::TDlg(TWindow *parent,TResId resId):
			TDialog(parent,resId)
{
  TEdit *edit;
  TValidator  *valid;

  // Создать  объект управления OWL для  каждого
  // управляющего  элемента  диалогового  окна,   который
  // будет участвовать  в передаче,   а  также подключить
  // объекты-валидаторы к областям
  // ввода,   которые     необходимо  проверить.
  new TEdit(this,ID_NAME,sizeof(transBuf.nameEdit));
  new TEdit(this,ID_ADDR,sizeof(transBuf.addrEdit));
  new TEdit(this,ID_CITY,sizeof(transBuf.cityEdit));
  edit = new TEdit(this, ID_STATE, sizeof(transBuf.stateEdit));
  valid = new  TFilterValidator("А-Я");
  edit->SetValidator(valid);
  edit = new TEdit(this,ID_ZIP,sizeof(transBuf.zipEdit));
  valid = new TFilterValidator("0-9-") ;
  edit->SetValidator(valid);
  edit = new TEdit(this, ID_PHONE, sizeof(transBuf.phoneEdit));
  valid = new TPXPictureValidator("###-###-####");
  edit->SetValidator(valid);
  edit = new TEdit(this, ID_BIRTH,sizeof (transBuf.birthEdit));
  valid = new TPXPictureValidator("##.##");
  edit->SetValidator(valid);
  //Назначить буфер обмена диалогового окна.
  TransferBuffer=&transBuf;
}

// TDlg::SetupWindow()
// Эта функция переопределяет SetupWindow() класса
// TDialog и вызывается перед выводом диалогового
// окна на экран. Она может использоваться для
// заключительной инициализации.
void TDlg::SetupWindow()
{
  // ВАЖНО! Необходимо выполнить обычную установку.
  TDialog::SetupWindow();
  // Сохранить текущие данные диалогового окна на
  // случай, если пользователь передумал и нажал кнопку Cancel.
  memcpy(&oldTransBuf, &transBuf, sizeof(transBuf));
}

// TDlg::CanClose()
//
// Эта функция, которая переопределяет функцию
// TWindows, позволяет программе выполнять
// заключительные операции перед закрытием
// диалогового окна.
BOOL TDlg::CanClose()
{
  // Копировать данные в буфер обмена.
  TransferData(tdGetData);
  // Проверить, не являются ли пустыми важные
  // текстовые поля и правильна ли их длина.
  if ((strlen(transBuf.nameEdit) == 0) ||
	  (strlen(transBuf.addrEdit) == 0) ||
	  (strlen(transBuf.cityEdit) == 0) ||
	  (strlen(transBuf.stateEdit) != 2) ||
	  (strlen(transBuf.zipEdit) < 5))
	{
	  // Если данные ошибочные, выдать сообщение
	  // пользователю и оставить на экране
	  // диалоговое окно для коррекции ошибок.
	  MessageBox ("Данные введены не все.", "Ошибка", MB_OK | 
                                  MB_ICONEXCLAMATION);
	  return FALSE; }
  else
		 // Если данные в порядке, разрешить закрытие диалогового окна.
  return TRUE;
}

// TDlg::CmCancel()
// Эта функция переопределяет функцию класса
// TDialog, реагирует на нажатие кнопки Cancel
// диалогового окна, восстанавливая буфер обмена
// в первоначальное состояние.
void TDlg::CmCancel()
{
  // Восстановить буфер обмена.
  memcpy(&transBuf, &oldTransBuf, sizeof(oldTransBuf));
  // Разрешить выполнение переопределенной функции CmCansel().
  TDialog::CmCancel();
}

void TApp::InitMainWindow()
{
  TFrameWindow *wndw= new TWndw(0,"Проверка достоверности вводимых данных");
  SetMainWindow(wndw);
  // Активизировать библиотеку DLL стандартных
  // настраиваемых элементов управления Borland.
  EnableBWCC();
}

int OwlMain(int,char *[])
{
  return TApp().Run();
}

    Файл ресурсов:

#ifndef WORKSHOP_INVOKED
#include "windows.h"
#endif

#define ADDRDLG         100
#define ID_NAME         101
#define ID_ADDR         102
#define ID_CITY         103
#define ID_STATE        104
#define ID_ZIP          105
#define ID_PHONE        106
#define ID_BIRTH        107
#define ID_DEFAULT      109
#define ID_CLEAR        110
#define CM_EXIT       24310
#define CM_TESTDIALOG   108
#define IDS_VALPXCONFORM    32520
#define IDS_VALINVALIDCHAR  32521


#ifdef RC_INVOKED

#include <owl\validate.rc>

ADDRDLG DIALOG 39, 34, 239, 124
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Address Form"
FONT 8, "MS Sans Serif"
{
 EDITTEXT ID_NAME, 42, 9, 188, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_ADDR, 42, 26, 188, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_CITY, 42, 44, 70, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_STATE, 140, 45, 16, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_ZIP, 180, 45, 50, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_PHONE, 43, 67, 65, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT ID_BIRTH, 147, 67, 45, 12, WS_BORDER | WS_TABSTOP
 LTEXT "Name:", -1, 17, 11, 20, 8
 LTEXT "Address:", -1, 9, 28, 29, 8
 LTEXT "City:", -1, 23, 47, 17, 8
 LTEXT "State:", -1, 116, 47, 20, 8
 LTEXT "Zip:", -1, 163, 46, 13, 8
 LTEXT "Phone:", -1, 16, 69, 24, 8
 LTEXT "Birthday:", -1, 113, 69, 30, 8
 DEFPUSHBUTTON "OK", IDOK, 12, 93, 37, 25
 PUSHBUTTON "Cancel", IDCANCEL, 62, 93, 39, 21
}


MENU_1 MENU
{
 POPUP "&File"
 {
  MENUITEM "E&xit", CM_EXIT
 }
 POPUP "&Dialog"
 {
  MENUITEM "&Test Dialog...", CM_TESTDIALOG
 }
}

#endif
Текст этого приложения можно взять здесь.

    Когда вы запустите эту программу и выберете команду Test Dialog в меню Dialog, вы увидите диалоговое окно, идентичное окну в программе шага 20 - то есть это диалоговое окно более не содержит кнопок Clear и Default. Когда диалоговое окно появляется, введите данные в его текстовые поля. Для вас будет открытием, что вы можете ввести что угодно в области ввода Name, Address и City. Однако, область ввода State может состоять только из заглавных букв, a Zip, Phone и Birthday - только из цифр. Более того, область ввода Phone допускает данные только в виде ##-##-##; a Date - ##.##.

    Если вы сделали ошибку при вводе данных, то вы увидите окно c сообщения, подобное изображенному на рисунке 1, когда вы попытаетесь покинуть область ввода.


Рис.1. Предупреждение о неправильном формате данных

    После того, как вы ввели все данные в диалоговое окно, программа проверяет, не содержит ли каждое поле символов больше, чем задано. При попытке покинуть диалоговое окно вы увидите окно сообщения, показанное на рисунке 2.


Рис.2. Предупреждение о пропуске данных

    Диалоговое окно остается открытым до тех пор, пока вы не введете правильные данные.

    Обратимся снова к примеру. В начале вы видите строку:

    #include <owl\vaidate.h>    . 

    Заголовочный файл VALIDATE.H должен быть включен в вашу программу, если вы хотите использовать классы проверки данных OWL. Заметим также, что класс TDlg кроме конструктора класса и функций SetupWindow() и CmCancel() включает функцию CanClose(). Функция CanClose() переопределяет функцию, унаследованную из класса TWindow и вызывается, когда пользователь пытается закрыть диалоговое окно.

    В действительности весь механизм проверки данных заключен в конструкторе класса диалогового окна:

TDlg::TDlg(TWindow *parent,TResId resId):
			TDialog(parent,resId)
{
  TEdit *edit;
  TValidator  *valid;

  // Создать  объект управления OWL для  каждого
  // управляющего  элемента  диалогового  окна,   который
  // будет участвовать  в передаче,   а  также подключить
  // объекты-валидаторы к областям
  // ввода,   которые     необходимо  проверить.
  new TEdit(this,ID_NAME,sizeof(transBuf.nameEdit));
  new TEdit(this,ID_ADDR,sizeof(transBuf.addrEdit));
  new TEdit(this,ID_CITY,sizeof(transBuf.cityEdit));
  edit = new TEdit(this, ID_STATE, sizeof(transBuf.stateEdit));
  valid = new  TFilterValidator("А-Я");
  edit->SetValidator(valid);
  edit = new TEdit(this,ID_ZIP,sizeof(transBuf.zipEdit));
  valid = new TFilterValidator("0-9-") ;
  edit->SetValidator(valid);
  edit = new TEdit(this, ID_PHONE, sizeof(transBuf.phoneEdit));
  valid = new TPXPictureValidator("##-##-##");
  edit->SetValidator(valid);
  edit = new TEdit(this, ID_BIRTH,sizeof (transBuf.birthEdit));
  valid = new TPXPictureValidator("##.##");
  edit->SetValidator(valid);
  //Назначить буфер обмена диалогового окна.
  TransferBuffer=&transBuf;
}

    В данном случае конструктор создает объекты TEdit, которые связаны областями ввода диалогового окна, участвующими в механизме передачи. Однако, вместо простого создания областей ввода эта функция также связывает объекты-валидаторы с областями ввода State, Phone и Birthday. Область ввода State связывается с TFilterValidator, который в данном случае устанавливает, что пользователь может вводить только заглавные буквы. Для связи валидатора с областью ввода вы должны выполнить следующие три шага:

    Описанная выше функция связывает область ввода State с объектом TFilterValidator. Данный тип валидатора проверяет каждый введенный пользователем символ и запрещает любые символы, не входящие в список зарезервированных символов. Конструктор TFilterValidator имеет в качестве единственного аргумента ссылку на объект TCharSet. Вам необходимо указать только строку символов, определяющую те символы, которые вы хотите включить в список. Вы можете использовать дефис для определения диапазона. Например, строка:

    new  TFilterValidator   ("A-Za-z0-9-");

создает фильтр проверки данных, который позволяет пользователю вводить только буквенные латинские символы (прописные и строчные), цифры и дефис. Заметим, что первые три дефиса определяют диапазон, тогда как четвертый дефис входит в список символов. Если сказать короче, строка new TFilterValidator("A-Z"); создает такой же фильтр проверки данных, что и строка new TFilterValidator("ABCDEFGHIJKLMNOPQRSTUVWXYZ");.

    Вы можете использовать для определения диапазона дефис либо перечисление всех символов - либо вы можете использовать комбинацию обоих способов.

    Если вернуться к конструктору класса диалогового окна, область ввода Zip также связана с объектом TFilterValidator, но, в данном случае валидатор разрешает только цифровые символы и дефис. Заметим, что в этом валидаторе отсутствует контроль длины строки введенных данных. Эту проверку выполняет диалоговое окно в своей функции CanClose().

    Поскольку имеет смысл выделить для области ввода Phone определенный формат, конструктор диалогового окна связывает с этой областью объект TPXPictureValidator. Объект TPXPictureValidator не только ограничивает тип возможных для ввода символов, но также onределяет формат, в котором могут вводиться эти символы.

    Конструктор TPXPictureValidator() имеет следующий общий вид:

    TPXPictureValidator (const  char  far  *pic,  BOOL  autofill=false);

где pic - шаблон, описывающим тип и формат данных, которые пользователь должен ввести (см. таблицу 1), autofill - по умолчанию имеет значение FALSE, является флагом, который сообщает конструктору, следует ли валидатору автоматически заполнять шаблон символами. Символы, используемые для описания шаблона ввода данных, приводятся в таблице 1.

Таблица 1. Символы, использующиеся в шаблоне TPXPictureValidator
Символ Описание
# Цифра
? Прописная или строчная буква
& Прописная буква
@ Любой символ
! Любой символ верхнего регистра
; Следующий символ воспринимать буквально
* Счетчик повторений
[] Опции
{} Операторы группировки
, Альтернатива

    Область редактирования Birthday также связана с объектом TPXPictureValidator. На этот раз средства проверки позволяют вводить только строки вида ##.##, который определяет формат даты.

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

    Именно вы определяете строки, которые появляются в окне сообщений вашего валидатора. Вы можете сделать это достаточно быстро, включив заголовочный файл VALIDATE.H в ваш файл ресурсов, как это сделано в примере. Вы можете также создать вашу собственную таблицу строк, используя перечисленные ниже строковые идентификаторы. IDS_VALPXCONFORM (32520) определяет сообщение об ошибке для TPXPictureValidator, IDS_VALINVALIDCHAR (32521) задает сообщение об ошибке для TFilerValidator, IDS_VALNOTINRANGE (32522) определяет сообщение об ошибке для TRangeValidator, a IDS_VALNOTINLIST (32523) определяет сообщение об ошибке для TLookupValidator. Эти идентификаторы строк используются функцией Error() соответствующего валидатора, которую вы можете, конечно, переопределить в ваших собственных классах валидаторов. Рисунок 3 показывает редактор ресурсов Resource Workshop совместно с определенной строковой таблицей средства проверки. Однако, если вас не очень беспокоит, какие строки отобразят в окне ошибок средства проверки данных, вы можете включить в вашу программу файлы ресурсов VALWATE.RC, VALIDATE.RN и файлы ресурсов. Эти файлы находятся в каталоге ..\INCLUDE\OWL.


Рис.3. Таблица строк-сообщений

    Средства проверки OWL не могут справиться с любой задачей проверки. Возможен случай, когда вы хотите выполнить что-нибудь выходящее за рамки их возможностей. Часто для решения таких задач вы можете создавать свои собственные классы валидаторов. Однако так же часто вы можете легко и просто выполнить ваши специальные проверки действительности данных внутри функции-члена CanClose() вашего диалогового окна, которая всегда вызывается перед закрытием диалогового (и любого окна) окна. В приведенной программе функция CanClose() класса диалогового окна проверяет длину определенных строк:

BOOL TDlg::CanClose()
{
  // Копировать данные в буфер обмена.
  TransferData(tdGetData);
  // Проверить, не являются ли пустыми важные
  // текстовые поля и правильна ли их длина.
  if ((strlen(transBuf.nameEdit) == 0) ||
	  (strlen(transBuf.addrEdit) == 0) ||
	  (strlen(transBuf.cityEdit) == 0) ||
	  (strlen(transBuf.stateEdit) != 2) ||
	  (strlen(transBuf.zipEdit) < 5))
	{
	  // Если данные ошибочные, выдать сообщение
	  // пользователю и оставить на экране
	  // диалоговое окно для коррекции ошибок.
	  MessageBox ("Данные введены не все.", "Ошибка", MB_OK | 
                          MB_ICONEXCLAMATION);
	  return FALSE; }
  else
		 // Если данные в порядке, разрешить закрытие диалогового окна.
  return TRUE;
}

    К тому моменту, когда OWL вызывает функцию CanClose(), передача данных из диалогового окна в буфер обмена еще не произошла. Поэтому, первое, что делает функция CanClose(), это вызывает TransData() с флагом TF_GETDATA, который приводит буфер обмена в соответствие с текущими данными, введенными пользователем в диалоговое окно. После того, как функция получит эти данные, проверить каждое поле структуры передачи для соответствующего данного не составляет труда. CanClose() проверяет, чтобы строки Name, Address, City не были пустыми, чтобы длина строки State была равна двум, и чтобы строка Zip была не меньше пяти символов. Если какая-нибудь из этих строк не отвечает данным требованиям, пользователю выдается окно с сообщением об ошибке.

    Со следующего шага мы начнем знакомиться с использованием стандартных диалоговых окон.




Предыдущий шаг Содержание Следующий шаг