На этом шаге мы рассмотрим пример возврата данных из диалогового окна.
Для простых диалоговых окон вы найдете в классе TDialog все, что необходимо для работы с ними. Но, наряду с этим, вам могут потребоваться от класса TDialog специальные возможности. Например, программа, представленная в предыдущем примере, создает диалоговое окно, содержащее несколько областей ввода. Если вы испытали окно диалога как следует, вы несомненно заметили, что хотя вы и можете вводить данные в области ввода, программа на них не реагирует.
К сожалению, при закрытии диалогового окна все элементы управления, а также их содержимое, исчезают вместе с ним. Однако существует простой способ сохранения содержания диалогового окна перед его закрытием, с помощью встроенного буфера обмена OWL. Вам необходимо только создать буфер для того типа информации, которую содержит ваше окно диалога, и связать этот буфер с вашим диалоговым окном. Когда пользователь закрывает окно диалога, используя кнопку OK, OWL автоматически передает содержимое управляющих элементов диалогового окна в ваш буфер перед закрытием окна диалога.
Этот механизм обмена делает управление диалоговыми окнами более удобным для программиста, но несколько замысловатым. Процедура включает несколько шагов, которые обязательно должны быть выполнены:
Сам буфер обмена представляет собой структуру, содержащую члены-данные, тип которых совпадает с типом управляющих элементов диалогового окна. Область ввода, например, требует массив символов; поэтому соответствующий ему член-данное в буфере обмена должен быть символьным массивом. С другой стороны, индикатор состояния требует целое число; член-данное буфера обмена должен быть целым числом.
Приведем текст программы, демонстрирующей, как работает механизм обмена данными 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 и дадим их описание.
Поле | Описание |
---|---|
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);
Затем функция вызывает 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() буфер обмена пуст, поэтому в окне появляются только метки.
Теперь вы можете рассмотреть механизм работы процесса передачи данных с точки зрения главного окна. Главное окно определяет буфер обмена диалогового окна, инициализирует его в зависимости от данных, которые будут появляться при его активизации, а затем выбирает данные из буфера после закрытия диалогового окна. Но как связать буфер данных с диалоговым окном? Эта связь управляется конструктором класса диалогового окна:
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, который не привязывается к диалоговому окну.
На следующем шаге мы рассмотрим настраиваемый диалоговый класс.