Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Имена и типы переменных-объектов диалогового окна




Идентификатор элемента управления Имя переменной Тип переменной
IDC_NAME objName CEdit
IDC_BIO objBio CEdit
IDC_SKILL objSkill CComboBox
IDC_DEPT objDept CListBox
IDC_CAT objCat CButton
IDC_RADIO2 задавать не нужно и невозможно
IDC_LIFE objLife CButton
IDC_DIS objDis CButton
IDC_MED objMed CButton
IDC_EDUC objEduc CComboBox
IDC_LANG objLang CComboBox
IDC_HARM objHarm CScrollBar
IDC_HYPO objHypo CScrollBar

 

В результате добавления этих переменных мастер включит их объявление в тело класса CDiaWinDlg (файл CDiaWinDlg.h), а также добавит код их инициализации в функцию CDiaWinDlg::DoDataExchange().

Программа должна компилироваться и выполняться (проверьте), но функциональность ее пока не изменится.

 

Шаг 8. Инициализация элементов управления диалогового окна в функции CDiaWinDlg::OnlnitDialog().

Мастер ИС сгенерировал для нас виртуальную функцию OnlnitDialog(), которая перекрывает функцию базового класса CDialog::OnlnitDialog(). Заметьте, что функция CDiaWinDlg::OnlnitDialog() должна обязательно содержать вызов функции базового класса CDialog::OnlnitDialog(). Обсуждаемая функция вызывается еще до прорисовки диалогового окна. Если в этой функции мы присваиваем некоторое значение связанной переменной, то это значение отобразится в соответствующем управляющем элементе окна при его показе.

Сгенерированный мастером ИС DDX-код (Dialog Data Exchange – обмен данными в диалоге) в функции CDiaWinDlg::DoDataExchange() не будет инициализировать данные в списках, поэтому нужно добавить соответствующий код вручную. Добавьте в конец функции CDiaWinDlg::OnlnitDialog() следующий код:

 
 

 

Метод InsertString() с первым параметром, равным -1, добавляет строку в конец списка. Им же можно было бы воспользоваться и для комбинированных списков вместо их инициализации в ресурсе – см. подсказку по InsertString(). Занести значение в элемент ввода CEdit можно с помощью функции SetWindowTextW(), как видно из примера. Другим способом инициализации CEdit является присваивание требуемого значения связанной с ним переменной, но это надо проделать до вызова CDialog::OnInitDialog(), т.е. в конструкторе класса диалога или в самом начале метода CDiaWinDlg::OnlnitDialog(). Еще одно место для инициализации – функция CDiaWin::InitInstance().

Занесите, самостоятельно и в полном сознании, начальный текст в поле «Биография». Компилируйте и тестируйте приложение. Теперь Вы должны видеть фамилию, биографию, список отделов. Однако полосы прокрутки по-прежнему должны быть непослушными и мы ими сейчас займемся вплотную.

Шаг 9. Подключение элементов управления «Полосы прокрутки».

Для полос прокрутки надо задать целочисленные константы, соответствующие крайнему левому и крайнему правому положению движка и, кроме того, можно даже задать начальное положение движка. Соответствующие константы удобно (а почему, собственно, как Вы думаете?) описать непосредственно в объявлении класса CDiaWinDlg (файл DiaWinDlg.h) в качестве перечисления:

 

Далее добавьте в конец функции CDiaWinDlg::OnlnitDialog() следующие операторы:

 

 

Запустив приложение на выполнение, Вы должны увидеть, что теперь движки полос прокрутки переместились из крайнего левого положения в соответствии с заданными значениями m_nHarm и m_nHypo, но их по-прежнему нельзя установить в желаемом положении, ну никак.

Для того чтобы движок полос прокрутки мог фиксироваться в установленной пользователем позиции, надо сгенерировать обработчик события Windows WM_HSCROLL ну и добавить в него свою логику. Сообщение WM_HSCROLL о перемещении движка любой горизонтальной полосы прокрутки посылаются объекту диалоговое окно, в нашем случае это объект класса CDiaWinDlg. Для генерации обработчика этого сообщения Windows, как и любого другого, можно поступить так:

· выделить мышью класс CDiaWinDlg на вкладке Class View окна решений;

· вызвать с помощью ПКМ контекстное меню, выбрать в нем тему Properties и щелкнуть по ней ЛКМ;

· в появившемся окне Properties щелкнуть кнопку Messages и в появившемся списке найти сообщение WM_HSCROLL;

· в столбце справа от имени сообщения с помощью ЛКМ выбрать команду <Add> OnHScroll.

 

В результате будет сгенерирована заготовка функции:

void CDiaWinDlg::OnHScroll(UINT nSBCode, UINT nPos,

CScrollBar* pScrollBar)

{

// Доделать: Добавьте свой код обработки сообщения и/или

// вызовите обработчик по умолчанию

CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

}

 

Доработаем ее:

 

Компилируйте и тестируйте приложение – теперь полосы прокрутки должны подчиняться пользователю.

Функции, связанные с полосами прокрутки, пользуются 16-разрядными целыми числами и для позиции движка, и для границ полос прокрутки. В приведенном обработчике обрабатываются не все сообщения от полос прокрутки, посылаемые Windows. Полный список возможных значений параметра nSBCode см. в MSDN.

Внимание, задание. В данной реализации обе полосы прокрутки имеют одинаковый диапазон возможных значений, заданный константами nMin и nMax. В жизни, однако, вредность и лицемерие имеют разные диапазоны возможных значений. Поэтому Вам предлагается совершить подвиг: самостоятельно доработать приложение так, чтобы у этих двух сущностей были разные диапазоны возможных значений. Причем для вредности надо выбрать диапазон, включающий и отрицательные значения, чтобы с помощью полосы вредность можно было задавать и полезность. Не исключено, что Вам понадобится функция CWnd::GetDlgCtrlID(), но не обязательно.

Шаг 10. Получение данных, введенных пользователем.

Диалоговое окно, если Вы еще не забыли, создается в функции CDiaWinApp::InitInstance(), которая находится в главном файле приложения – DiaWin.cpp. Там же оно и закрывается. Вот костяк этой функции, с комментариями (несущественные для данного анализа операторы – и только для него! – опущены):

BOOL CDiaWinApp::InitInstance()

{

CDiaWinDlg dlg; // создание экземпляра класса окно

INT_PTR nResponse = dlg.DoModal(); // отображение окна на мониторе

if (nResponse == IDOK)

{

// Сюда надо поместить код, который должен выполняться

// при нажатии пользователем клавиши OK

}

else if (nResponse == IDCANCEL)

{

// Сюда надо поместить код, который должен выполняться

// при нажатии пользователем клавиши Cancel

}

 

// Так как диалог завершился, очевидно, надо вернуть FALSE

// для завершения приложения

return FALSE;

}

 

В данном – учебно-тренировочном – приложении мы просто будем выводить введенные пользователем данные в окно Debug с помощью макросов TRACE, естественно, в том случае, когда пользователь завершит работу с окном с помощью кнопки ОК. Удобство макросов TRACE состоит еще и в том, что в окончательной версии программы они не работают и вообще не встраиваются в код.

Как ни странно, но выводить значения строк CString для приложений, скомпонованных с использованием кодировки Unicode (для кодировки ANSI такой проблемы нет), с помощью макросов TRACE невозможно: вместо ожидаемых символов английского языка или кириллицы выводятся «случайные» символы. Имеется функция WideCharToMultiByte(), которая позволяет преобразовать строку символов из кодировки Unicode в кодировку ANSI, в частности. Так как эта функция имеет много параметров, автор данного труда разработал собственную функцию-оболочку, облегчающую использование функции WideCharToMultiByte(). Вот ее текст:

 

Приведенный код можно поместить непосредственно перед функцией CDiaWinApp::InitInstance() либо в другом подходящем месте. Понятно, что функция не может обрабатывать строки длиной более чем 1024 символов, но мне сдается, что для отладки этого может хватить. С использованием этой чудо-функции CDiaWinApp::InitInstance() для нашего приложения можно записать так:

 

 
 

 

Тестируйте приложение, проверьте корректность вывода результатов в окно Debug, проанализируйте их. Обратите внимание, что вызов макроса в таком виде

TRACE("Образование = %s Биография = %s\n",ToAnsi(dlg.m_strEduc),

ToAnsi(dlg.m_strBio));

 

приведет к выводу ошибочных данных, а именно два раза будет выводиться строка dlg.m_strEduc. Другими словами, не указывайте функцию ToAnsi() более одного раза в качестве параметра макроса.

Обратите внимание также на то, что происходит при нажатии клавиши Enter в момент ввода текстовых данных в каком-либо элементе управления — диалоговое немедленно закрывается. А что происходит при нажатии клавиши Escape?.

 

Разбор приложения

 

После вызова функции Cdialog::DoModal() управление возвратится вашей программе, только когда пользователь закроет диалоговое окно. Если Вам это ясно, то Вы гений, который постиг тайну модального диалогового окна. Перейдя к работе с немодальными диалоговыми окнами, Вы еще оцените, как просто было программировать модальные диалоговые окна, потому что при вызове DoModal() очень многое остается за кадром. Но вернемся к нашей теме и рассмотрим, «кто кого вызывает»:

CDialog::DoModal()
CDiaWinDlg::OnInitDialog()
…дополнительная инициализация…

CDialog::OnInitDialog()
CWnd::UpdateData(FALSE)
CDiaWinDlg::DoDataExchange()
пользователь вводит данные…

пользователь нажимает кнопку OK

CDiaWinDlg::OnOK()
… дополнительная проверка …

CDialog::OnOK
CWnd::UpdateData(TRUE)
CDiaWinDlg::DoDataExchange()
CDialog::EndDialog(IDOK)

 

OnInitDialog() и DoDataExchange() — виртуальные функции, переопределенные в классе CDiaWinDlg. Windows вызывает OnlnitDialog() при инициализации диалогового окна, что приводит к вызову виртуальной функции DoDataExchange(), которую сгенерировал для Вас мастер. Взгляните на листинг этой функции, размещенной в Вашем файле DiaWinDlg.cpp. Функция DoDataExchange() и функции DDX_(обмен) и DDV_(проверка достоверности) — «двусторонние». Если функция UpdateData() вызывается с параметром FALSE, то она переносит данные из связанных переменных в элементы управления диалогового окна. Если же она вызывается с параметром TRUE, то передает данные в противоположном направлении: из элементов управления в связанные переменные. DDX _ Text переопределяется для подстройки под множество типов данных.

Функция EndDialog() играет главную роль в процедуре завершения диалогового окна. DoModal() возвращает параметр, передаваемый ей функцией EndDialog(). Значение параметра IDOK означает, что пользователь завершил диалог нажатием кнопки ОК или клавиши Enter, a IDCANCEL – что пользователь завершил диалог нажатием кнопки Cancel. Вы можете самостоятельно вызвать EndDialog() при наступлении какого-либо события – пример см. в MSDN.

Совет. Вы можете написать собственные DDX-функции и подключить их к Visual C++. Это бывает весьма полезно, если во всей своей программе Вы пользуетесь уникальным типом данных. Подробнее см. MFC Technical Note #26 в интерактивной справочной системе MSDN (для версии MVS 6.0).

 

Усовершенствование приложения

 

Один из законов Мэрфи гласит: «Совершенство достигается к моменту краха». Будем оптимистами и все же попытаемся усовершенствовать нашу программу. Избавим наше приложение от скверной привычки завершать свою работу при, даже случайном, нажатии клавиши Enter (ну, например, вследствие падения головы или какой-либо более важной части тела на клавиатуру).

Шаг 11. Обработка нажатия кнопки ОК.

В нашем приложении виртуальная функция CDialog::OnOK() обрабатывает щелчок кнопки ОК, что запускает процесс обмена данными и процедуру выхода из диалогового окна. Тот же результат дает и клавиша Enter — может быть, это как раз то, чего Вы хотели, а может быть – и нет. Если пользователь нажмет клавишу Enter, скажем, в поле ввода «Фамилия», его немедленно «выбросит» из диалогового окна. Вряд ли это пользователю понравится, если он не мазохист …

Что же происходит? В момент нажатия этой клавиши Windows ищет кнопку, на которой установлен фокус ввода (input focus) — на экране он выглядит как дополнительная пунктирная рамка внутри рамки кнопки при ее нажатии.

 

Замечание. Советую запустить программу, вызвать окно диалога и внимательно к нему присмотреться: нетрудно заметить, что кнопка ОК имеет более толстые границы, чем другие. Теперь добавьте в окно «ничего не делающую» дополнительную кнопку Button1, попробуйте нажимать клавишу табуляции, выделите кнопку Button1 и нажимайте клавишу Enter – ничего не произойдет. Обратите внимание также на то, что в классе CDiaWinDlg сейчас нет обработчика нажатия кнопки OK, как, впрочем, и кнопки Cancel. Вместе с тем в классе CDialog имеется виртуальная функция OnOK(), как и функция OnCancel().

 

Если фокус не установлен ни на одну кнопку, Windows ищет указанную программой или в ресурсе кнопку по умолчанию (default pushbutton): у такой кнопки более толстые границы. Если такой кнопки нет, вызывается виртуальная функция ОnОК — даже если кнопка ОК отсутствует.

Клавишу Enter можно «отключить», просто написав ничего не делающую функцию CDiaWinDlg::OnOK() и добавив код завершения в новую функцию, реагирующую на щелчок кнопки ОК. Последовательность действий такова.

1. На вкладке Resource View отыщем идентификатор IDD_DIAWIN_DIALOG нашего окна и щелкнем по нему ЛКМ, вызвав тем самым появление нашего окна в окне графического редактора. Выберем кнопку ОК, щелкнем по ней ПКМ и в появившемся контекстном меню выберем команду Add Event Handler (добавить обработчик события). Появится окно (рис. 9), в поле которого Function Handler Name необходимо предлагаемое по умолчанию имя OnBnClickedOk заменить на OnOK (обратите внимание на регистр символов!) и нажать кнопку Add and Delete. Мастер сгенерирует функцию

void CDiaWinDlg::OnOK()

{

// TODO: Add your control notification handler code here

OnOK();

}

 

Как видите, мастер не такой уж и мастер: функция вызывает саму себя, что при запуске приложения на выполнение и нажатии клавиши Enter приведет к переполнению стека. Надо в нашем обработчике вызывать (виртуальную) функцию CDialog::OnOK() родительского класса – тогда все будет ОК, т.е. по-прежнему. Однако мы должны на этом этапе сделать нашу функцию просто пустышкой с одним только макросом TRACE:

void CDiaWinDlg::OnOK()

{

TRACE(“Вход в CDiaWinDlg::OnOK()\n”);

}

 

Если теперь мы запустим приложение на выполнение, то нажатие кнопки ОК или клавиши Enter не будет приводить к закрытию окна, т.е. завершению программы, так как мы устранили вызов функции CDialog::OnOK(). В окне Debug мы должны видеть сообщение о вызове функции CDiaWinDlg::OnOK(). Однако нашей задачей является все же обеспечение такой логики работы приложения, при которой нажатие кнопки ОК будет приводить к завершению работы программы.

 

2. Вызовите окно свойств для кнопки ОК и замените идентификатор кнопки IDOK на IDC_OK и отмените у нее свойство Default Button, присвоив ему значение False (рис. 10). Теперь нажмите кнопку Control Events в окне Properties и сгенерируйте для нее обработчик события BN_CLICKED. Мастер сгенерирует функцию CDiaWinDlg::OnBnClickedOk(), в которую достаточно добавить одну-единственную строку и вызов макроса TRACE для наглядности:

void CDiaWinDlg::OnBnClickedOk()

{

TRACE("Вход в CDiaWinDlg::OnBnClickedOk()\n");

CDialog::OnOK();

}

 

Обратите внимание на вызов функции CDialog::OnOK(): эта функция делает полезную работу.

 

 
 

Рис. 9. Добавление обработчика события нажатия кнопки ОК

 

3. Соберите и протестируйте приложение. Попробуйте теперь нажать клавишу Enter. Ничего произойти не должно, но в окне Debug появится вывод макроса TRACE «Вход в CDiaWinDlg::OnOK()». Вместе с тем по щелчку кнопки ОК диалоговое окно должно закрыться.

 

 
 

Рис. 10. Изменение свойств кнопки ОК

Шаг 12. Обработка нажатия кнопки Cancel.

Так же как нажатие клавиши Enter приводило к вызову ОnОК(), так и нажатие клавиши Esc инициирует вызов OnCancel(), в результате чего диалоговое окно завершается с кодом возврата IDCANCEL от функции DoModal(). Наша программа не предусматривает особой обработки IDCANCEL, поэтому нажатие клавиши Esc (или щелчок кнопки Close) закрывает диалоговое окно. Вы можете обойти этот процесс, применив функцию-заглушку OnCancel() по аналогии с тем, как это уже делалось для кнопки ОК.

Замечание. Попробуйте изменить какие-нибудь значения в управляющих элементах окна и закрыть его – диалоговое окно – с помощью кнопки ОК и с помощью кнопки Cancel. Понаблюдайте при этом за значениями элементов управления (точнее, связанных с ними переменных-членов) в окне Debug. Есть разница?

 

Шаг 13. Выбор фона диалогового окна и цвета элементов управления.

Чтобы изменить фон отдельных диалоговых окон или конкретных элементов управления в каком-либо диалоговом окне, придется (в отличие от работы в Delphi) потрудиться. Каждый элемент управления, прежде чем появиться на экране, посылает родительскому диалоговому окну сообщение WM_CTLCOLOR. Такое же сообщение отправляется и самому диалоговому окну. Если написать в производном классе диалогового окна обработчик этого сообщения, можно установить цвет текста и его фон, а также выбрать кисть для заполнения нетекстовой области элемента управления или диалогового окна.

Ниже приведен пример функции OnCtlColor(), задающей желтый фон для всех полей ввода и синий фон с красной штриховкой для диалогового окна. Для цвета фона текста полей ввода, исключительно в качестве иллюстрации, задан зеленый цвет. В результате цветовая гамма диалогового окна получается ужасной, но зато наглядной.

В функции CDiaWinDlg::OnCtlColor() параметр nCtlColor определяет тип элемента управления, а pWnd — конкретный элемент управления. Чтобы установить цвет отдельного элемента управления, нужно преобразовать pWnd в идентификатор дочернего окна и проверить его значение, как это сделано в приведенной ниже функции.

 

Заготовку функции CDiaWinDlg::OnCtlColor() можно создать с помощью все того же окна свойств Properties для нашего диалогового окна, выбрав на вкладке Messages сообщение WM_CTLCOLOR и сгенерировав обработчик этого сообщения. Текст обработчика приведите в соответствие с приведенным выше.

Переменные m_hYellowBrush и m_hRedBrush – это переменные-члены типа HBRUSH (brush – кисть), которые предлагается описать в классе CDiaWinDlg, в секции protected. Эти переменные можно инициализировать в CDiaWinDlg::OnInitDialog(), например, так:

 
 

 

 

Попробуйте собрать приложение и тестировать его. Если Вам не понравится цветовая гамма – измените!

 

Замечание. Диалоговое окно не помещает сообщение WM_CTLCOLOR в очередь сообщений (message queue); вместо этого окно, чтобы немедленно отправить сообщение, вызывает Win32-функцию SendMessage(). Благодаря этому обработчик сообщения может вернуть параметр, в данном случае описатель кисти. Это не MFC-объект типа CBrush, a Win32 HBRUSH. Создать кисть можно, вызвав Win32-функции CreateSolidBrush(), CreateHatchBrush(), CreatePatternBrush() и др.

В функции CDiaWinDlg::OnCtlColor() можно получить информацию о конкретном элементе управления несколькими способами, например, путем анализа значения указателя на элемент управления:

if(*pWnd==objBio)

{

pDC->SetBkColor(RGB(255, 0, 0)); // цвет фона текста биографии

pDC->SetTextColor(RGB(255,192,128)); // цвет фона текста биографии

TRACE("IDC_BIO\n");

return m_hYellowBrush; // кисть элемента управления

// или return CreateSolidBrush(RGB(150,150,255));

}

 

Шаг 14. Сохранение результатов диалога в текстовом файле. Доработайте программу таким образом, чтобы введенные пользователем данные сохранялись в текстовом файле по нажатию кнопки «Сохранить», которую Вы добавите в диалоговое окно. Для этой благородной цели вы можете воспользоваться подсказками, приведенными в подразделе «Запись текстовых файлов с использованием класса CFile» файла VC_Lect.doc. В этом же файле вы найдете подсказки, как добавить в свою программу диалог по выбору имени файла (класс CFileDialog).

 

 

Коварно-контрольные вопросы:

1. Как вызвать системное меню диалогового окна, какие команды в нем есть?

2. Вызовите окно «О программе» диалогового окна, а также покажите в программе код, который вызывает это окно.

3. Какие Вы знаете способы задания заголовка окна (в сценарии – «Кадры решают все!»)?

4. Какие комбинированные списки (ComboBox) позволяют только выбирать одно из имеющихся значений, а какие позволяют также и вводить собственные значения? Какое свойство комбинированного списка ответственно за такое поведение элемента управления?

5. Зачем нужна функция ToAnsi и что это за функция: библиотечная или собственная?

6. Чем полезен макрос TRACE, чем он отличается от просмотра значений переменных с помощью средств отладчика?

7. Вызов какой функции приводит к появлению диалогового окна на экране?

8. Для чего предназначен макрос RGB, чем его можно было бы заменить?

9. Для чего предназначена функция Format класса CString?

 

 


Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...