Шаг 20.
Библиотека OWL.
Настройка класса TDialog

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

    Для простых диалоговых окон вы найдете в классе TDialog все, что необходимо для работы с ними. Но, наряду с этим, вам могут потребоваться от класса TDialog специальные возможности. Например, программа, представленная в предыдущем примере, создает диалоговое окно, содержащее несколько областей ввода. Если вы испытали окно диалога как следует, вы несомненно заметили, что хотя вы и можете вводить данные в области ввода, программа на них не реагирует.

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

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

  1. Создание буфера обмена данными, соответствующего типу данных, которые вы хотите получить из диалогового окна.
  2. Создание управляющих объектов для каждого управляющего элемента диалогового окна, участвующего в передаче данных.
  3. Присваивание члену-данному ТransBuffer диалогового окна адреса вашего буфера обмена.

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

    Приведем текст программы, демонстрирующей, как работает механизм обмена данными OWL.

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

// Структура буфера обмена.
struct
{
  char nameEdit[81];
  char addrEdit[81];
  char cityEdit[26];
  char stateEdit[3];
  char zipEdit[11];
  char phoneEdit[20];
  char birthEdit[9];
} 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
{
	public:
		TDlg(TWindow *parent,TResId resId);
};


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)/4;
	Attr.H=GetSystemMetrics(SM_CXSCREEN)/5;
        // Инициализировать буфер обмена. 
        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)
{
  //Создаем управляющий объект  OWL для каждого
  //управляющего элемента диалогового окна.
  new TEdit(this,IDC_NAME,sizeof(transBuf.nameEdit));
  new TEdit(this,IDC_ADDR,sizeof(transBuf.addrEdit));
  new TEdit(this,IDC_CITY,sizeof(transBuf.cityEdit));
  new TEdit(this,IDC_STATE,sizeof(transBuf.stateEdit));
  new TEdit(this,IDC_ZIP,sizeof(transBuf.zipEdit));
  new TEdit(this,IDC_PHONE,sizeof(transBuf.phoneEdit));
  new TEdit(this,IDC_BIRTH,sizeof(transBuf.birthEdit));
  //Назначить буфер обмена диалогового окна.
  TransferBuffer=&transBuf;
}

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 IDC_NAME        101
#define IDC_ADDR        102
#define IDC_CITY        103
#define IDC_STATE       104
#define IDC_ZIP         105
#define IDC_PHONE       106
#define IDC_BIRTH       107
#define CM_EXIT       24310
#define CM_TESTDIALOG   108


#ifdef RC_INVOKED

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 IDC_NAME, 42, 9, 188, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_ADDR, 42, 26, 188, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_CITY, 42, 44, 70, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_STATE, 140, 45, 16, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_ZIP, 180, 45, 50, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_PHONE, 43, 67, 65, 12, WS_BORDER | WS_TABSTOP
 EDITTEXT IDC_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, 76, 92, 33, 21
 PUSHBUTTON "Cancel", IDCANCEL, 126, 93, 33, 21
}


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

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

    Когда вы запустите программу, появится окно, показанное на рисунке 1.


Рис.1. Основное окно программы

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

    Для ввода диалогового окна выберите команду Test Dialog из пункта меню Dialog. Когда диалоговое окно появится, введите что-нибудь по вашему желанию в области ввода, а затем "нажмите" кнопку ОК. Когда вы сделали это, главное окно изменит свой вид, показывая введенные вами данные. Если вы выберете кнопку Cancel, чтобы выйти из диалогового окна, данные не изменятся.

    Класс приложения не содержит ничего неожиданного, кроме вызова функции EnableBWCC() в InitMainWindow():

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

    EnableBWCC() открывает библиотеку настраиваемых элементов управления Borland, позволяющую использовать трехмерные элементы управления Borland.

    В начале примера объявляется структура:

struct
{
  char nameEdit[81];
  char addrEdit[81];
  char cityEdit[26];
  char stateEdit[3];
  char zipEdit[11];
  char phoneEdit[20];
  char birthEdit[9];
} transBuf;

    Это - буфер обмена с диалоговым окном. Он содержит символьный массив для каждой области ввода в диалоговом окне. В нем нет членов для кнопок диалогового окна, потому что программа не нуждается в хранении информации, получаемой от кнопок. Только управляющие элементы, участвующие в передаче данных, требуют области хранения в буфере обмена. В случае областей ввода размер символьного буфера определяется максимальным количеством символов, разрешенных пользователю для ввода, с учетом хранения замыкающего нулевого символа. В описанном выше буфере обмена область ввода для поля имени позволяет ввести максимум 80 символов. Если бы поле было определено как nameEdit[5], пользователь смог бы ввести максимум 4 символа.

    Поскольку буфер обмена объявлен вне какого-либо класса, любой класс в программе может осуществить доступ к этой структуре.

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

        memset (&transBuf, 0, sizeof(transBuf));

    Класс главного окна объявляет две функции - CmTestDialog() и Paint(). Первая выводит диалоговое окно, когда пользователь выбирает команду Test Dialog пункта меню Dialog:

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

    Первая строка этой функции создает объект TDialog из диалогового окна с идентификатором ресурса ADDRDLG и присвоением переменной dialog адреса этого объекта. Заметим, что программа создает экземпляр класса TDlg, а не экземпляр класса TDialog OWL. Хотя TDlg и наследуется из класса TDialog, класс TDlg имеет свой собственный конструктор, который обеспечивает специфическое поведение класса. Вторая строка вызывает функцию Execute() диалогового объекта, которая выводит диалоговое окно на экран. Когда программа отображает диалоговое окно, OWL автоматически копирует содержимое буфера обмена в управляющие элементы диалогового окна. Для выхода из диалогового окна пользователь должен нажать кнопку ОК или Cancel. (Если пользователь закрывает диалоговое окно командой Close системного меню, результат аналогичен выбору кнопки Cancel.)

    Если пользователь выходит по нажатию кнопки OK, OWL переносит значения, содержащиеся в управляющих элементах диалогового окна, в буфер обмена, откуда программа может получить данные. Если пользователь выходит по нажатию кнопки Cancel, OWL пропускает шаг передачи данных, оставляя буфер обмена в первоначальном состоянии.

    В функции CmTestDialog(), Execute() возвращает идентификатор выбранной кнопки. Этот идентификатор сохраняется в result. Если значение result равно IDOK, программа должна отобразить новые данные. В этом случае вызывается функция Invalidate() (унаследованная из TWindow), которая в свою очередь вызывает Windows API функцию InvalidateRect() для информирования окна о том, что его рабочая область нуждается в перерисовке. Это, конечно, приводит к вызову функции Paint() окна, которое перерисовывает диалоговое окно.

    Paint() сначала вызывает GetTextMetrics() (TDC-версия функции Windows API с этим же именем) для получения размера текущего шрифта:

    paintDC.GetTextMetrics(tm);

    Единственным аргументом этой функции является ссылка на структуру TEXTMETRIC. GetTextMetrics() возвращает информацию о шрифте в структуре TEXTMETRIC, которая содержит обширную информацию о шрифте, включающую не только его размер, но также его атрибуты, шаг и семейство шрифта, внутреннее и внешнее представление и многое другое. В Paint() программе необходима только высота шрифта (находится в элементе tmHeight структуры TEXTMETRIC), которая требуется для определения интервала между строками.

    Приведем перечень полей структуры TEXTMETRIC и дадим их описание.

Таблица 1. Поля TEXTMETRIC и их описание
Поле Описание
tmHeight Высота символа в логических единицах.
tmAscent Высота символа выше относительно базовой линии в логических единицах.
tmDescent Высота символа ниже относительно базовой линии в логических единицах.
tmInternalLeading Разница между размером точки и физическим размером шрифта.
tmExternalLeading Расстояние между строками текста.
tmAveCharWidth Средняя ширина символов в логических единицах.
tmMaxCharWidth Максимальная ширина символов в логических единицах.
tmWeight Указывает нормальный или жирный шрифт.
tmItalic Ненулевое значение указывает курсив.
tmUnderlined Ненулевое значение указывает шрифт с подчеркиванием.
tmStruckOut Ненулевое значение указывает шрифт с перечеркиванием.
tmFirstChar Код первого символа шрифта.
tmLastChar Код последнего символа шрифта.
tmDefaultChar Символ, отображаемый в Windows для символов, не включенных в отобранный шрифт.
tmBreakChar Символ, используемый для интервала между словами.
tmPitchAndFamily Шаг и семейство шрифтов.
tmCharSet Определяет набор символов шрифта.
tmOverhand Дополнительное расстояние, необходимое для символов курсива и жирного шрифта.
tmDigitizedAspectX и tmDigitizedAspectY Соотношение аспектов устройства, для которого был выбран шрифт.

    После получения размера шрифта Paint() вызывает SetTextAlign() для установки режима выравнивания текста вправо:

    paintDC.SetTextAlign(TA_RIGHT);

    Единственный аргумент этой функции содержит битовые флаги, которые показывают, каким образом текст будет располагаться на экране. Эти флаги включают TA_LEFT для левого выравнивания, TA_RIGHT - для правого выравнивания, и TA_CENTER для центрирования текста.

    Затем, Paint() вызывает GetTextExtent() для определения длины строки "BIRTHDAY:"

  TSize size=paintDC.GetTextExtent("BIRTHDAY:",9);

    Эта строка является самой длинной текстовой строковой меткой, которую программа должна вывести в окне, поэтому ее длина определяет границу правого выравнивания всех меток.

    GetTextExtent() возвращает размер строки - длину и высоту в логических единицах - в объект TSize, а данном случае в объект size типа TSize.

    Общий вид функции GetTextExtent() следующий:

    Bool GetTextExtent(const char far* string, int stringlen);
где string - строка, stringlen - количество символов в строке.


    Замечание. Поскольку текст в окне часто выводится с использованием пропорциональных шрифтов, вы не можете с помощью числа символов в строке определить, сколько места она займет на экране. Для решения этой задачи Windows содержит функцию GetTextExtent(), которая возвращает длину строки не в символах, а в логических единицах (обычно пикселях).

    Затем функция вызывает TextOut() для вывода текстовых меток в окно, используя size.cx, который является длиной строки в логических единицах, чтобы вычислить позиции для ввода каждой метки, и tm.tmHeight, который представляет высоту текущего шрифта в логических единицах, чтобы определить интервал между строк:

  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:");

    После вывода меток, Paint() изображает каждый элемент из буфера обмена после соответствующей ему метки. Для выполнения этого Paint() сначала устанавливает режим выравнивания текста влево:

  paintDC.SetTextAlign(TA_LEFT);

а затем вызывает TextOut() для каждого элемента данных, вновь используется size. сх для определения вертикального положения и tm.tmHeight для определения горизонтального положения:

  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);

    Конечно, при первом вызове Paint() буфер обмена пуст, поэтому в окне появляются только метки.


    Замечание. Функции GetTextMetrics(), SetTextAlign(), GetTextExtent(), TextOut() являются OWL-версиями функций Wmdows API с теми же именами. Все эти четыре функции являются частью класса TDC OWL.

    Теперь вы можете рассмотреть механизм работы процесса передачи данных с точки зрения главного окна. Главное окно определяет буфер обмена диалогового окна, инициализирует его в зависимости от данных, которые будут появляться при его активизации, а затем выбирает данные из буфера после закрытия диалогового окна. Но как связать буфер данных с диалоговым окном? Эта связь управляется конструктором класса диалогового окна:

TDlg::TDlg(TWindow *parent,TResId resId):
			TDialog(parent,resId)
{
  //Создаем управляющий объект  OWL для каждого
  //управляющего элемента диалогового окна.
  new TEdit(this,IDC_NAME,sizeof(transBuf.nameEdit));
  new TEdit(this,IDC_ADDR,sizeof(transBuf.addrEdit));
  new TEdit(this,IDC_CITY,sizeof(transBuf.cityEdit));
  new TEdit(this,IDC_STATE,sizeof(transBuf.stateEdit));
  new TEdit(this,IDC_ZIP,sizeof(transBuf.zipEdit));
  new TEdit(this,IDC_PHONE,sizeof(transBuf.phoneEdit));
  new TEdit(this,IDC_BIRTH,sizeof(transBuf.birthEdit));
  //Назначить буфер обмена диалогового окна.
  TransferBuffer=&transBuf;
}

    Конструктор выполняет два действия. Во-первых, он создает объект управления для каждого управляющего элемента, которые будут участвовать в обмене, создавая эти объекты в том же порядке, в котором соответствующие им члены-данные появляются в структуре буфера обмена. (Заметьте, что для использования класса TEdit в заглавную часть программы включается заголовочный файл класса EDIT.H.) Конструктор связывает буфер обмена с диалоговым окном через назначение полю TransferBuffer (наследуемому из класса Twindow) адреса буфера обмена.

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

    На следующем шаге мы рассмотрим настраиваемый диалоговый класс.




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