Модальный диалог с органами управления и функциями отклика

В примере 28-2 рассматривается простая (и крайне несовершенная) программа, создающая набор "карточек" с данными о сотрудниках некоторого предприятия. Карточки заполняются с помощью мо­дального диалогового окна, в котором предусмотрен ряд органов управления: несколько полей для ввода текста, а также альтернативные и нажимаемые кнопки (рис. 28.3). Созданный набор карточек (базу дан­ных) можно записать на диск в файл с произвольным именем и заданным расширением dbf; можно также прочитать и вывести на экран содержимое файла с базой данных.

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

• создание модального диалога с разнообразными органами управления

• обработка сообщений от органов управления диалогового окна

• использование буфера обмена для обмена данными между главным и диалоговым окнами

• использование стандартных диалогов Windows для чтения и записи файлов

В последующих разделах этой главы в программу 28-2 будут внесены усовершенствования, которые позволят обсудить некоторые дополнительные аспекты организации диалоговых окон.

//Приложение 28-2. Модальные диалоги

//Файл 28-2.h # define INPUTDLG 100 #define CM_INPUT 201 #define CM_OPEN 202 #define CM_SAVE 203 #define CM_EXIT 24310 #define IDC_NAME 101 #define IDC_JOB 102 #define IDC_YEAR 103 #define IDC_M 104 #define IDC_F 105 #define IDC_ADD 106 #define MAXENTRIES 20 /*Структура буфераобмена.*/ typedef struct{

char nameEdit[20];

char jobEdit[20];

char yearEdit[5];

bool mEdit;

bool fEdit;

}TSB;

//Файл 28-2.rc # include "28-2.h" MainMenu MENU{ POPUP "Файл"{

MENUITEM "Ввод данных...",CM_INPUT MENUITEM "Открыть...",CM_OPEN


258 Глава 28

MENUITEM "Сохранить...",CM_SAVE MENUITEM SEPARATOR MENUITEM "Выход",CM_EXIT }

}

INPUTDLG DIALOG 7,37,222,130

STYLE DS_MODALFRAME|WS_POPUP| WS_VISIBLE|WS_CAPTION|WS_SYSMENU CLASS "bordlg"

CAPTION "Форма для заполнения карточек" FONT 8, "MS Sans Serif"{

EDITTEXT IDC_NAME,69,12,137,12

RTEXT "Фамилия",-1,11,15, 53, 9

EDITTEXT IDC_JOB,69,35,137,12 Ч

RTEXT "Должность",-1,10,38, 54, 9

EDITTEXT IDC_YEAR,69,58,37,12

RTEXT "Год рождения",-1,11, 61, 54, 9

CONTROL "M",IDC_M,"BUTTON",BS_AUTORADIOBUTTON,123, 58, 20,12

CONTROL "Ж",IDC_F,"BUTTON",BS_AUTORADIOBUTTON, 162, 58,20,12

CONTROL "Добавить",IDC_ADD,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE,40, 93, 37, 25

CONTROL "Закрыть",IDOK,"BorBtn",BS_PUSHBUTTON|WS_CHILD|WS_VISIBLE,145,93,37, 25

CONTROL '",-1,"BorShade",BSS_HDIP|BSS_LEFT|WS_CHILD|WS_VISIBLE ,6,80,206,2

}

//Файл 28-2.cpp

#include <owl\framewin.h>

#include <owl\dialog.h>

#include <owl\edit.h>

#include <owl\checkbox.h>

#include <owl\opensave.h>

#include "28-2.h"

#include <stdio.h>

/*Глобальная переменная*/

TSB tsbArray [MAXENTRIES]; //Массив карточек

/*Класс приложения, производный от Tapplication*/

class MyApp:public TApplication{

public:

void InitMainWindowt); //Замещаем функцию InitMainWindow

/*Класс главного окна, производный от TframeWindow*/

class MyWindow:public TFrameWindow{

public:

MyWindow(TWindow*parent,char far*title);

void Paint(TDC&,bool,TRect&); //Замещаем функцию Paint ()

void CmInput();// Функция отклика на пункт "Ввод данных"

void CmOpen();// Функция отклика на пункт "Открыть"

void CmSave();// Функция отклика на пункт "Сохранить"

DECLARE_RESPONSE_TABLE(MyWindow);

};

/*Класс окна диалога, производный от Tdialog*/ class MyDialog:public TDialog{

int index;// Номер текущей карточки

TSB tsb; //Структурная переменная для буфера обмена

public:

MyDialog(TWindow*,TResId);

void CmAdd();

DECLARE_RESPONSE_TABLE(MyDialog);

};

/*Таблица откликов класса MyWindow*/ DEFINE_RESPONSE_TABLE1(MyWindow,TFrameWindow)

EV_COMMAND(CM_INPUT,CmInput),

EV_COMMAND(CM_OPEN,CmOpen),

EV_COMMAND(CM_SAVE,CmSave), END_RESPONSE_TABLE; /*Конструктор главного окна*/ MyWindow::MyWindow(TWindow*parent,char far*title):TFrameWindow(parent,title) {

AssignMenu("MainMenu");

}

/*Функция откликов класса MyWindow*/ void MyWindow::CmInput(){

memset(&tsbArray,0,sizeof(TSB)*MAXENTRIES); //Очистим массив карточек

new MyDialog(this,INPUTDLG)->Execute() ;//Выполняем диалог

Invalidate(); //Активизируем сообщение WM_PAINT для перерисовки главного окна


Диалоговые окна 259

}

void MyWindow::CmOpen(){

TOpenSaveDialog::TData fileData(OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|

OFN_HIDEREADONLY,"Базы данных (*.dbf)| *.dbf|"); int result= new TFileOpenDialog(this,fileData)->Execute(); if(result==IDCANCEL)return;

char* ptr=strchr(fileData.FileName,'\0'); //Получим указатель на конец имени if(result!=IDOK|jstrcmpi(ptr-3,"DBF")) {//Если файл не открылся или не.DBF MessageBox("Неверное имя файла","Info",МВ_ОК); return; }

FILE* fp=fopen(fileData.FileName,"rb"); if(!fp)return;

fread(tsbArray,sizeof(TSB),MAXENTRIES,fp); fclose(fp); Invalidate(); } void MyWindow::CmSave(){

TOpenSaveDialog::TData fileData(OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

"Базы данных (*.dbf)|*.dbf|);

int result= new TFileSaveDialog(this,fileData)->Execute(); if(result==IDCANCEL)return;

char* ptr=strchr(fileData.FileName,'\0'); //Получим указатель на конец имени if(result!=IDOK||strcmpi(ptr-3,"DBF")) {//Если файл не открылся или не.DBF MessageBox("Неверное имя файла","Info",МВ_ОК); return; }

FILE* fp=fopen(fileData.FileName,"wb"); if(!fp)return;

fwrite(tsbArray,sizeof(TSB),MAXENTRIES,fp); fclose(fp); }

/*Функция Paint()*/

void MyWindow::Paint(TDC&dc,bool,TRect&){ char s[80];

for(int i=0;i<MAXENTRIES;i++){ strcpy(s,tsbArray[i].nameEdit); strcat(s," ");

strcat(s,tsbArray[i].jobEdit); strcat(s," ");

strcat(s,tsbArray[i].yearEdit); strcat(s," "); if(tsbArray[i].mEdit==true)

strcat(s,"м"); else if(tsbArray[i].fEdit==true)

strcat(s,"ж");

dc.TextOut(5,i*20,s); } }

/*Конструктор класса MyDialog*/

MyDialog::MyDialog(TWindow*parent,TResId resId):TDialog(parent,resId){ new TEdit(this,IDC_NAME,sizeof(tsb.nameEdit)); new TEdit(this,IDC_JOB,sizeof(tsb.jobEdit)); new TEdit(this,IDC_YEAR,sizeof(tsb.yearEdit)); new TCheckBox(this,IDC_M); new TCheckBox(this,IDC_F);

TransferBuffer=&tsb; //Назначили tsb буфером обмена memset(&tsb,0,sizeof(TSB)); index=0; //Начинаем с карточки #0 }

/*Таблица откликов класса MyDialog*/ DEFINE_RESPONSE_TABLE1(MyDialog,TDialOg)

EV_COMMAND(IDC_ADD,CmAdd), END_RESPONSE_TABLE;

/*Единственная функция отклика класса MyDialog*/ void MyDialog::CmAdd(){ TransferData(tdGetData);

memmove(&tsbArray[index++],&tsb, sizeof (TSB)); memset(&tsb,0,sizeof(TSB)); TransferData(tdSetData); GetWindowPtr(GetParent())->Invalidate();}


260 Глава 28

/*Замещающая функция InitMainWindow ()*/

void MyApp::InitMainWindow(void){

EnableBWCC();

SetMainWindow(new MyWindow(0,"Программа 28-2"));

}

/*Главная функция приложения OwlMain*/ int OwlMain(int,char*[]){

return MyApp (). Run(); }

Заголовочный файл 28-2.h содержит определения констант-идентификаторов пунктов меню и орга­нов управления диалогом, константу MAXENTRIES, задающую максимальный объем создаваемой базы данных, а также описание структуры TSB, которая будет использована для обмена данными с диалого­вым окном. Следует отметить, что произвольное имя TSB, определенное с помощью оператора typedef, является типом структуры, а не именем структурной переменной. Структурные переменные типа TSB (целых две) будут объявлены в тексте программы. Состав членов структуры TSB соответствует составу данных, вводимых нами в поля диалогового окна: фамилия сотрудника nameEdit, его должность jobEdit, год рождения в символьной форме yearEdit, а также булевы переменные mEdit и fEdit, говорящие о поле сотрудника. Первая переменная устанавливается в 1 для сотрудников-мужчин, вторая - для женщин.


В файле ресурсов 28-2.гс определена форма главного меню приложения (рис. 28.4), а также содержимое диалогового окна. Диалоговое окно, как и в предыдущем примере, получено с по­мощью программы Resource Workshop и выполнено в стиле Bor­land. Окна ввода текста представляют собой управляющие эле­менты класса EDITTEXT, альтернативные кнопки относятся к классу BUTTON со стилем BS_AUTORADIOBUTTON, для на­жимаемых кнопок использован класс Borland BorBtn, а для пояс­няющих надписей - класс RTEXT (чтобы выровнять надписи по правому краю). Для разнообразия в диалог введена разделяющая полоса в стиле Borland класса BorShade.

Переходя к рассмотрению текста программы, следует сделать общее замечание относительно порядка описания ее составляю­щих. Вообще типичная объектно-ориентированная программа со-

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

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

Для обработки сообщений от первых трех пунктов главного меню в таблицу откликов класса главно­го окна MyWindow включены три предложения EV_COMMAND, а в класс главного окна - объявления трех функций отклика CmInput(), CmOpen() и CmSave. Пункт "Выход", которому назначен стандартный идентификатор СМ_ЕХIТ, обрабатывается системой, и для него функция отклика не нужна. Действия, выполняемые функциями отклика, а также назначение функции Paint() главного окна будут описаны позже.

Поскольку класс, описывающий поведение диалогового окна, должен в данном случае иметь специ­фическую таблицу откликов, отвечающую составу органов управления диалогом, он должен быть не классом OWL, а прикладным классом, производным от класса TDialog. В состав членов этого класса, на­званного в программе MyDialog, включены вспомогательная переменная index, структурная переменная tsb типа TSB, которая будет использована для обмена данными с диалогом, конструктор, таблица откли­ков и функция обработки сообщений от кнопки "Добавить" CmAdd(). Объявление таблицы откликов для класса диалога выполняется так же, как и для класса главного окна, за исключением того, что в качестве параметра макроса DECLARE_RESPONSE_TABLE указывается имя соответствующего класса, в данном случае имя MyDialog.

Сама таблица откликов содержит единственный макрос EV_COMMAND (с указанием функции от­клика CmAdd()), так как из всех составляющих диалогового окна мы будем напрямую обрабатывать


Диалоговые окна 261

только сообщения от кнопки "Добавить". Сообщения от остальных элементов диалога (полей ввода и альтернативных кнопок) будут обрабатываться встроенными средствами OWL, описанными ниже.

При выборе пункта "Ввод данных" главного меню вызывается (через таблицу откликов класса Му-Window) функция CmInput(). В ней обнуляется массив карточек, после чего создается объект диалогово­го окна и для него вызывается функция Execute(). Пользователю предоставляется возможность заполнить карточки базы данных требуемым содержимым. После завершения работы с диалогом (нажатием кнопки "ОК") функция Execute() завершается, управление возвращается в функцию CmInput(), и с помощью вы­зова функции Invalidate() инициируется перерисовка главного окна, в которое выводится информация из всех заполненных карточек базы данных.

Рассмотрим теперь вопрос о взаимодействии с управляющими элементами диалога. В нашем приме­ре требуется извлекать данные из полей ввода диалогового окна (а также направлять в поля ввода дан­ные, если мы хотим заполнять эти поля не с клавиатуры, а программно) и получать информацию о нажа­тии альтернативных кнопок "М" и "Ж". Для реализации встроенных средств коммуникации с управляю­щими элементами диалога необходимо прежде всего определить специальную структуру - буфер обме­на, элементы которого соответствуют данным, поступающим от управляющих элементов. В нашем при­мере эта структура получила наименование TSB (от Transfer Buffer); в нее входят три символьные строки для приема данных из полей ввода и две булевы переменные для получения информации о состоянии альтернативных кнопок. Длина символьных строк, определенных в структуре TSB, задает максимальное число символов, которое можно будет ввести в соответствующую строку. При определении длины строк не следует забывать о завершающем символьную строку нуле; именно из-за него строка yearEdit. для приема года имеет длину не 4, а 5 байт.

Определив буфер обмена и создав в программе соответствующую переменную (у нас она имеет имя tsb и помещена в класс MyDialog), необходимо для каждого управляющего элемента диалога создать управляющий объект соответствующего класса OWL. Все управляющие классы выводятся из базового класса TControl, который сам является одним из производных класса TWindow (см. рис. 28.5, где показа­на часть структуры классов управляющих элементов, главным образом тех, которые используются в рас­сматриваемом или последующих примерах).

Для поля ввода (класс органа управления EDITTEXT, см. файл ресурсов 28-2.гс) создается объект класса TEdit, для альтернативной кнопки (класс органа управления BUTTON, стиль BS_AUTORADIOBUTTON) - объект класса TCheckBox, для списка - объект класса TListBox, для ком­бинированного окна - объект класса TComboBox и т.д. Создание управляющих объектов удобнее всего выполнить в конструкторе диалогового класса, причем порядок их создания должен строго соответство­вать порядку объявления соответствующих элементов в структуре буфера обмена. Так, для буфера обме­на нашего примера

typedef struct{

char nameEdit[20]; char jobEdit[20]; char yearEdit[5]; bool mEdit; bool fEdit; }TSB;

требуется такая последовательность вызова конструкторов управляющих классов:

new TEdit(this,IDC_NAME,sizeof(tsb.nameEdit)); new TEdit(this,IDC_JOB,sizeof(tsb.jobEdit)); new TEdit(this,IDC_YEAR,sizeof(tsb.yearEdit)); new TCheckBox(this,IDC_M); new TCheckBox(this,IDC_F);


262 Глава 28

Как видно из приведенного фрагмента, конструкторы управляющих объектов могут иметь разный формат, но все они требуют в качестве первого параметра указатель на класс диалогового окна, в кото­ром эти объекты расположены; поскольку мы создаем объекты в конструкторе этого самого класса, то в качестве указателя на класс удобно использовать указатель this. Вторым параметром выступает иденти­фикатор соответствующего органа управления. Третий параметр, где он есть - это длина передаваемой через буфер строки текста. Заметим, что имена создаваемых объектов мы в программе использовать не будем, поэтому здесь, как и в других аналогичных случаях, вместо полной формы предложения с опера­тором new, например,

TCheckBox* checkBox1=new TCheckBox(this,IDC_F);

вполне допустима краткая форма без указания имени указателя на создаваемый объект.

Последней операцией по организации взаимодействия с диалогом является назначение нашей струк­турной переменной tsb буфером обмена. Это делается с помощью предложения

TransferBuffer=&tsb; //Назначили tsb буфером обмена

где TransferBuffer - это защищенный член класса TWindow, который рассматривается OWL, как указа­тель на буфер обмена. Присвоив ему адрес нашей структурной переменной tsb, мы тем самым заставили OWL использовать tsb в качестве буфера обмена.

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

tsb.mEdit=true;

а для предприятия с преобладанием женщин - предложение

tsb.mEdit=true;

Обмен данными с диалогом реализуется вызовом функции класса TWindow TransferData(), которая в качестве параметра требует указания одного из двух флагов - tdGetData в случае передачи данных из диалога в буфер обмена и tdSetData при передаче данных из буфера в диалог. В программе обмен данны­ми осуществляется в функции CmAdd() отклика на нажатие кнопки "Добавить". Прежде всего вызовом функции TransferData() данные из полей диалога передаются в буфер обмена tsb, затем функцией mem-move() данные копируются из буфера обмена в массив карточек с наращиванием индекса заполняемого элемента в этом массиве и, наконец, после очистки буфера его содержимое переносится в диалог, очищая поля диалога и облегчая тем самым ввод следующих данных. Если на этом функцию CmAdd() завер­шить, то хотя вводимые с клавиатуры данные будут накапливаться в массиве карточек tsbArray, на экра­не их видно не будет. Для того, чтобы каждая вводимая карточка сразу же отображалась в главном окне, надо вызвать функцию Invalidate() для этого окна. Однако мы, создавая объект главного окна в функции MyApp::InitMainWindow(), не позаботились оставить для будущих ссылок ни имя этого объекта, ни ука­затель на него, что лишило нас возможности вызывать для этого объекта какие-либо функции-члены. Придется, как мы это уже не раз делали, получить значение указателя динамически, для чего в классе TWindow предусмотрен соответствующий набор функций. В последнем предложении функции CmAdd()

GetWindowPtr(GetParent())->Invalidate();

функция GetParent() возвращает значение дескриптора (типа HWND) родительского, т.е. главного окна, а функция GetWindowPtr() с этим дескриптором в качестве аргумента возвращает указатель на главное ок­но, который и используется для вызова функции Invalidate().


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: