программирование, создание программ, учебник Delphi, учебник по программированию, бейсек, делфи, си, паскаль
 
 
 

 

Анализатор кодов ошибок


Приложение на основе диалога


Уверен, что многие из читателей, разрабатывая свой код или запустив чужой многообещающий код, получали сообщение типа:
0xC000000S STATUS_ACCESS_VIOLATION
Раньше, во времена DOS, довольно часто можно было видеть сообщение «General Protection Fault» («Общая ошибка защиты»). Сообщения такого рода иногда вызывают чувство беспомощности и досады. Первая мысль — случилось что-то ужасное, вторая — неужели нельзя поподробнее объяснить причину отказа? Теперь, во времена COM (Component Object Model — Модель многокомпонентных объектов), количество ошибок и различного рода несоответствий драматически возрастает. В файле WinError.h, который можно найти в папке Include Visual Studio 6, содержатся описания свыше 1300 кодов ошибок, а в 7-й версии этот же файл содержит описания уже свыше 2500 кодов. Не будет преувеличением сказать, что не каждый разработчик будет с энтузиазмом искать файл WinError.h, а затем искать в нем код своей очередной ошибки.
Анализируя файл WinError.h, можно заметить, что есть две категории ошибок. Первая категория — коды \Win32-ошибок, которые имеют вид десятичного числа, например:
#define ERROR_ACCESS_DENIED 5L
Надеюсь, вы не забыли, что суффикс ' L' задает тип константы (long). Вторая категория — это коды ошибок, возвращаемых многими OLE- и СОМ-АР1-функци-ями, например:
#define E_NOTIMPL _HRESULT_TYPEDEF_(0x80004001L)
Последние имеют вид шестнадцатеричных чисел, которые хранятся в переменных типа HRESULT.
Примечание
Этот тип не является еще одним описателем (Handle), как может показаться, судя по его имени. Он определен в файле WTypes.h оператором эквивалентности typedef LONG HRESULT и используется как набор битовых полей, имеющих определенный смысл. Самым необычным в этой ситуации является то, что суть ошибки (ее текстовое описание), то есть то, что более всего интересует программиста, присутствуете файле WinError.h в виде комментария. Вот фрагмент файла, который описывает одну ошибку:
// Messageld: ERROR_FILE_NOT_FOUND
//
// MessageText:
//
// The system cannot find the file specified.
//
#define ERROR FILE NOT FOUND 2L
В файле есть несколько макроопределений, которые позволяют выделять некоторые параметры сообщения, но они не дают возможности программным способом выделить поле MessageText, так необходимое нам с вами. В файле приведены описания двух форматов кодов ошибок. Один из них определяет \¥ш32-ошибки, а другой — СОМ-ошибки. Оба имеют сходную структуру, но различаются в трактовке старших разрядов. Общее для них поле (Code) содержит относительный код или позицию ошибки в группе ошибок, связанных с той или иной ветвью в дереве Windows-технологий. Группы заданы кодом Facility. Например, группа, определяемая кодом Facility=3, объединяет ошибки работы с памятью, а группа Facility=17 объединяет все коды ошибок, которые могут возникнуть при использовании технологии СОМ+. Мощность множества вариаций атрибута Facility в версии Studio.Net 7.0 больше (23), чем в Visual Studio 6 (16), так как возросло количество поддерживаемых Windows технологий.
Таблица. 4.1 Формат кода Win32-ошибок

Таблица. 4.2 Формат HRESULT СОМ-ошибок

Символы имеют следующий смысл:

  • с — Customer code flag (флаг пользователя);
  • R — Reserved (зарезервировано для будущего использования);
  • s — Severity (Успех или неудача);
  • Rl, Cl, N, г — зарезервированная часть кода Facility.

Два старших бита Win32-oum6oK кодируют такие категории степени ошибки:

  • 00 —Success (Успех);
  • 01 — Informational (Информационное сообщение);
  • 10 — Warning (Предупреждение);
  • 11 — Error (Отказ).

Зададимся целью разработать приложение, которое можно назвать анализатором кодов ошибок. С его помощью пользователь, зная код ошибки, сможет быстро получить всю информацию, которая хранится в файле WinError.h и связана именно с этой ошибкой. На примере разработки приложения мы продемонстрируем такие технологические приемы, как:

  • создание приложения на основе диалога;
  • работа с текстовыми строками и потоками ввода-вывода, определенными в STL (Standard Template Library);
  • использование стандартного диалога по поиску папки; О поиск в реестре Windows.

Основная идея приложения заключается в том, что при его открытии происходит попытка с помощью реестра найти файл WinError.h, сканировать его и заполнить динамический контейнер структур с информацией обо всех ошибках. Далее пользователь имеет возможность либо просматривать информацию об ошибках, последовательно проходя по элементам контейнера, либо ввести код ошибки и увидеть результат его трансляции (расшифровки). Форматы битовых полей HRESULT подсказывают состав полей структуры, которую можно использовать для хранения информации об ошибке:
//====== Тип стуктур для описания ошибок
struct ErrorType
{
string Code;
// Код ошибки string Identifier;
// Ее идентификатор string Message;
// Текстовое описание
//======= Конструктор с параметрами
ErrorType(string с, string i, string m)
{
Code = c;
Identifier = i;
Message = m;
}
};
Так как мы собираемся использовать контейнер структур такого типа, то полезно определить новый тип:
typedef vector<ErrorType> ERROR_VECTOR;
Определения такого типа упрощают создание ссылок на контейнеры или на его составные части. Перед тем как мы приступим к разработке приложения, отметим, что MFC-приложения на основе диалога имеют некоторые преимущества перед другими типами приложений. Главным из них является простота структуры классов и возможность пользоваться глобальными функциями MFC для обмена данными между окнами и переменными диалогового класса. Надо признать, что Эти функции (типа DDX_— Dynamic Data Exchange И DDV_ — Dynamic Data Validation) очень удобны и надежны. Конечно, приложения рассматриваемого типа не обладают такими разнообразными возможностями, как приложения типа MDI (Multiple Document Interface), но для определенного класса задач они являются оптимальным выбором.
Начнем с создания стартовой заготовки приложения, основанного на диалоге. Тип приложения, как вы помните, выбирается с помощью мастера MFC Application Wizard. В левой части своего окна он имеет список команд, которые играют роль вкладок, раскрывающих различные окна-страницы правой части окна, поэтому команды слева мы будем называть вкладками, а окна справа — страницами. Для создания заготовки:

  1. В меню File t New выберите команду Project.
  2. В появившемся диалоге New Project, в окне Project Type раскройте ветвь дерева под именем Visual C++ Projects и выберите ветвь Win32 Projects.
  3. В окне Templates выберите тип проекта: Win32 Project.
  4. В окне Name задайте имя проекта: Look. В окне Location задайте или оставьте без изменения местоположение новой папки с файлами решения (solution).
  5. В окне мастера MFC Application Wizard выберите вкладку Application Type и в окне справа укажите тип MFC-приложения — Dialog based.
  6. Выберите вкладку User Interface Features и введите заголовок окна диалога — Look for an Error Code, так как английский язык в ресурсах работает значительно надежней русского, по крайней мере в бета-версии Studio.Net 7,0. Снимите флажок About — это упростит стартовое приложение.
  7. Перейдите на страницу Advanced Features и снимите флажок ActiveX Controls.
  8. Проанализируйте содержимое страницы Generated Classes. Здесь вы можете изменить имена двух классов, которые создаст мастер, но лучше этого не делать, так как имена составляются по стандартной схеме, которая упрощает обмен идеями в сообществе разработчиков.
  9. Нажмите кнопку Finish. После непродолжительной паузы вы должны увидеть окно Solution Explorer.

Примечание
Если это (или какое-то другое) окно оказалось в режиме Auto Hide, а вам он не подходит, то надо сделать окно активным и вменю Window снять флажок с команды Auto Hide. Окно перейдет в режим Docable. Эти же действия надо проделать со всеми другими окнами студии, которые вы хотите поместить в блок страниц, открываемых вкладками. Любое окно можно также перевести в режим Floating и вытащить из блока страниц. Для того чтобы снова вставить его в блок, надо перевести его в режим Docable, «взять» за заголовок и указать его новое.место среди вкладок блока. Важно то, что указатель мыши должен находиться в этот момент в области ярлыков вкладок.
Запустите стартовую заготовку и убедитесь, что она создает диалог со значком, двумя кнопками и текстом «TODO:..» Раскройте окно Class View и найдите на его панели инструментов кнопку с подсказкой Class View Sort By. Опробуйте все способы сортировки содержимого окна Class View. Наиболее удобным является режим Sort By Type, однако для начинающих будет полезен режим более подробной демонстрации классов и методов (Sort Group By Type). Выберите этот режим и раскройте узел с именем класса CLookDlg. Этот класс, происходящий от класса coialog, выполняет функции главного окна приложения. Теперь раскройте узел Functions и дважды щелкните на конструкторе класса. Просмотрите коды конструктора и других методов класса.
Вставьте в начало файла LookDlg.h (после директивы #pragma) определение типа структур ErrorType, которое было рассмотрено выше. Перед тем как мы начнем вносить другие изменения, упростим заготовку. Функция OnPaint, реагирующая на сообщение WM_PAINT, обычно не используется в диалоговых окнах, так как элементы управления, которыми начинен диалог, нет необходимости перерисовывать. Их перерисовывает каркас приложения без нашего участия. Каждый элемент управления имеет свою, зарегистрированную системой, оконную процедуру, которая и выполняет перерисовку. Однако в заготовке функция OnPaint присутствует, и она выполняет задачу, которая имеет малую важность, — перерисовывает значок на кнопке приложения (taskbar button) в его свернутом состоянии. Странным кажется тот факт, что пока мы-даже не можем свернуть окно. Вы заметили, что оно не имеет кнопки MinimizeBox, которая обычно располагается в правом верхнем углу окна. Запустите приложение и проверьте это. Сейчас мы исправим ситуацию, а заодно решим задачу со значком. Выполните следующие шаги для изменения класса CLookDlg:

  1. Перейдите в окно Resource View, раскройте дерево ресурсов и дважды щелкните на идентификаторе диалога IDD_LOOK_DIALOG.
  2. Откройте окно Properties, в разделе Appearance найдите свойство MinimizeBox и измените его значение на TRUE.
  3. В окне Resource View, раскройте узел Icon, выберите идентификатор значка IDR_ MAINFRAME и нажмите клавишу Delete.
  4. В окне редактора кодов (LookDlg.cpp) целиком удалите тела двух функций OnPaint, OnQueryDraglcon, два элемента карты сообщений: ON_WM_PAINT и ON_WM_QUERYDRAGICON и строку вызова Loadlcon из тела конструктора класса.
  5. В файле LookDlg.h удалите объявления этих функций и переменную HICON m_hlcon.

Теперь в тело функции OnlnitDialog вместо двух строк:
SetIcon(m_hlcon, TRUE);
// Set big icon Setlcon(m_hlcon, FALSE);
// Set small icon
вставьте три строки, которые функционально заменяют весь тот код, который мы убрали. Функция Loadlcon загружает значок. Так как первый параметр функции задан равным нулю, то она не будет искать значок в ресурсах приложения, а возьмет стандартный (predefined) с идентификатором IDI_WINLOGO. Вы знаете, что символы «::», стоящие перед именем функции, означают, что эта функция является глобальной, то есть API-функцией. Эти символы можно и убрать, но тогда мы нарушим конвенцию (договоренность) об именах, существующую в сообществе программистов:
HICON hMylcon = ::Loadlcon(0,IDI_WINLOGO);
Setlcon(hMylcon, TRUE);
// Set big icon Setlcon(hMylcon, FALSE);
// Set small
Запустите приложение и убедитесь, что окно диалога теперь сворачивается и значок на месте.
  
Дизайн диалога
Развитие диалогового приложения обычно начинают с размещения элементов управления на поверхности его окна — шаблона диалога. Откройте шаблон в окне редактора и включите панель инструментов Dialog Editor, если она еще не включена.
Примечание
Список доступных панелей можно увидеть, вызвав контекстное меню над пустым местом планки меню. Кроме того, нам понадобится окно Toolbox. Если его нет или вы его закрыли, то дайте команду View > Toolbox. В окне Toolbox нажмите кнопку Dialog Editor, и тогда в нем появится достаточно длинный список элементов управления, которые можно размещать на форме диалога.
Итак, вы вооружены и экипированы для того, чтобы создать лицо диалога. Вы будете выбирать элементы управления из окна Toolbox и размещать их в окне шаблона. В этот момент свойства текущего элемента отражены в окне Properties. Так как вам одновременно понадобятся окна Resource View, Properties и Look.re, то будет удобно, если вы переведете окно Properties в режим Floating, вынете его из блока страниц и переместите в более удобное место.
  
Управление окном Toolbox
При работе с окном диалога вам приходится пользоваться инструментальным окном Toolbox, которое имеет вкладки (tab) несколько необычного вида. Все доступные вкладки инструментов вы сможете увидеть, если воспользуетесь контекстным меню окна Toolbox и выберете команду Show > All > Tabs. Нажимая на заголовки вкладок, вы увидите, что пространство между ними заполняется списками тех элементов управления, которые соответствуют выбранной вкладке. Большинство из этих элементов пока недоступны, но они станут доступны в определенных ситуациях. Сейчас мы рассмотрим, как можно управлять вкладками и их содержимым. Вы можете:

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

Среди всех вкладок инструментов (tools) есть две особые, которые носят названия General и Clipboard Ring. Они всегда демонстрируются по умолчанию. Другие же вкладки появляются и исчезают в зависимости от контекста, то есть от типа редактора, с которым вы в данный момент работаете. Если выбрать какой-либо элемент на какой-либо вкладке и нажать клавишную комбинацию Shift+Ctrl+C, то этот элемент попадает на вкладку кольцевого буфера инструментов (Clipboard Ring). Нажатие Shift+Ctrl+V восстанавливает в окне текущей вкладки последний элемент из буфера. Повторное нажатие восстанавливает следующий элемент из кольцевого буфера. При многократном повторении Shift+Ctrl+V элементы кольцевого буфера циклически повторяются. Так написано в документации, но в бета-версии такого поведения не наблюдается.
Для того чтобы добавить новую вкладку в окно Toolbox, надо щелкнуть правой клавишей мыши в окне и выбрать команду Add > Tab. Затем ввести имя вкладки во временном текстовом окне и нажать ENTER. Такие вкладки рекомендуют использовать для создания своего любимого набора инструментов. Один инструмент — стрелка выбора (Pointer) по умолчанию присутствует на всех вкладках. Для удаления вкладки надо выбрать команду Delete » Tab в контекстном меню, вызванном на заголовке вкладки. В документации эта команда называется Remove > Tab, а в действительности (в бета-версии) — Delete > Tab.
Для того чтобы поместить на вкладку какой-либо элемент, надо выбрать в контекстном меню команду Customize > Toolbox и выбрать в окне диалога из двух списков (рис. 4.1) нужный элемент, включив флажок выбора слева от элемента, и нажать кнопку ОК. С помощью подобных же действий, но выключая флажок, можно убрать элемент из окна инструментов. Кнопка Browse диалога Customize > Toolbox позволяет найти элемент, который не обозначен в списке. Но помните, что многие СОМ-объекты не будут работать в проектах Studio.Net, если они не зарегистрированы, то есть не отражены в окнах диалога.

Команда Rename > Tab из контекстного меню заголовка вставки позволяет переименовать всю вставку, а команда Rename > Item из контекстного меню самой вставки позволяет переименовать элемент. Команда Show > All > Tabs работает по принципу переключателя. Команда List > View, работающая по этому же принципу, позволяет переключать режим просмотра инструментов (значки/список). Команда Sort > Items > Alphabetically из контекстного меню заголовка вставки позволяет отсортировать инструменты, а команды Move > Up или Move > Down из меню окна — переместить их.
  
Создаем диалог
Важным моментом в этой процедуре является то, что каждый элемент управления должен быть идентифицирован в поле ID окна Properties. Другие свойства можно изменять в других полях этого окна, но большая часть из них уже имеет нужные значения. Только некоторые свойства следует изменить. Перечислим в табл. 4.3 (в порядке слева направо, сверху вниз) элементы управления и их идентификаторы.
Таблица 4.3. Идентификаторы элементов управления диалога

Проверьте особые свойства элементов, которые должны быть такими, как показано ниже. Если они другие, то введите коррективы:

  • IDC_SPIN — SetBuddylnteger: TRUE;
  • IDC_CURRENT — Readonly: TRUE;
  • IDC_SLIDER— AutoTicks: TRUE, Point: Top/Left, TickMarks: TRUE;
  • IDC_RIGHT — Image: IDI_EYERIGHT;
  • IDC_LEFT — Image: IDI_EYELEFT.

  
Реакция окна на уведомляющие сообщения
Наш анализатор кодов ошибок по сути является браузером (инструментом для просмотра) файла WinError.h с особой структурой. Мы хотим дать пользователю возможность выбрать один из двух вариантов просмотра:

  • последовательный, с помощью счетчика Spin Control или ползунка slider Control,
  • поиск по значению, задаваемому в поле элемента IDC_FIND типа Edit Control.

Отметим, что счетчик (spinner) раньше назывался Up-Down control, а ползунок (slider) — Trackbar Control. Знание этого факта помогает понять, почему сообщения (стили) счетчика содержат аббревиатуру UD, а ползунка — тв. Все три используемых элемента (счетчик, ползунок и поле редактирования IDC_FIND) должны быть синхронизированы так, чтобы изменение позиции счетчика отслеживалось ползунком, и наоборот. Ввод пользователем кола ошибки в поле редактирования IDC_FIND должен вызывать мгновенную реакцию приложения и отражаться в показаниях счётчика и ползунка, но только в случае, если требуемый код найден.
Примечание
Напомним, что элементы управления на форме диалога являются дочерними окнами (child-windows) по отношению к окну диалога. И этот тип отношений parent-child (родство) не является отражением наследования в смысле ООП. Он существовал до того, как была создана MFC. Важно то, что дочерние окна генерируют уведомляющие сообщения об изменениях, происходящих в них а система направляет их в оконную процедуру родительского окна.
Здесь мы должны использовать способность родительских (parent) окон реагировать на уведомляющие сообщения, исходящие от их «детей». Сообщения такого типа можно условно поделить на старые (Windows 3.x) и новые (Win32). Старых много, так как каждый тип элементов имеет несколько уведомляющих сообщений. Посмотрите Help > Index, задав индекс EN_ (Edit Notifications), и вы увидите, что элемент типа окна редактирования уведомляет своего родителя о таких событиях, как: EN_CHANGE (изменился текст), EN_KILLFOCUS (окно теряет фокус ввода), EN_UPDATE (окно готово к перерисовке) и множестве других.

Наряду со старыми существует одно новое универсальное событие WM_NOTIFY. Теперь при создании новых элементов управления не надо плодить сообщения типа WM_*, которых и так очень много. Все могут обойтись одним — WM_NOTIFY. Его универсальность состоит в том, что все новые типы элементов умеют генерировать это одно сообщение. В дополнение они сопровождают его указателем на структуру NMHDR (Notify Message Header), которая способна «привести» за собой различные другие структуры. Весь трюк состоит в том, что, получив это сообщение вместе с указателем NMHDR* pNMHDR, который на самом деле показывает на другую, более сложную структуру, класс родительского окна знает тип элемента и, следовательно, знает, к какому типу надо привести этот указатель. Например, при изменении показаний счетчика система посылает родительскому окну сообщение WM_NOTIFY, в IParam которого помещен указатель типа NMHDR*:
typedef struct tagNMHDR
{
//=== Описатель окна (счетчика), пославшего сообщение
HWND hwndFrom;
//=== Идентификатор окна (счетчика)
UINT idFrora;
//=== Код сообщения
OINT code;
}
NMHDR;
Но на самом деле указатель pNMHDR содержит адрес другой структуры:
typedef struct _NM_UPDOWN
{
//====== Вложенная структура
NMHDR hdr;
//====== Текущая позиция счетчика
int iPos;
//====== Предлагаемое увеличение показаний
int iDelta;
}
NMUPDOWN, FAR *LPNMUPDOWN;
Так как структура hdr типа NMHDR стоит первой в списке полей NMUPDOWN, то все законно — присланный в iParam указатель действительно показывает на NMHDR, но в составе NMUPDOWN. Эту ситуацию легче запомнить, а может быть, и понять, если использовать аналогию. Способ запоминания замысловатых выкладок с помощью глупых аналогий известен давно. Мне приходит в голову такая: звонят в дверь (WM_NOTIFY), вы подходите к ней и видите, что пришел знакомый мальчик (NMHDR) с сообщением, но, открыв дверь, вы обнаруживаете, что за ним стоит широкоплечий мужчина (NMUPDOWN). Теперь пора ввести в класс CLookDlg реакции на уведомляющие сообщения:

  1. Откройте шаблон диалога и установите курсор мыши на счетчике (IDC_SPIN).
  2. В окне Properties нажмите кнопку с подсказкой ControlEvents.
  3. В появившемся списке уведомляющих сообщений, которые генерирует счетчик, выберите UDN_DELTAPOS, а в ячейке справа укажите действие — <Add>.

Перейдите в окно LookDlg.cpp и найдите в карте сообщений новый элемент
ON_NOTIFY(UDN_DELTAPOS, IDC_SPIN, OnDeltaposSpin)
который был вставлен инструментом ClassWizard и который означает, что если окну диалога, управляемому классом CLookDlg, придет сообщение UDN_DELTAPOS (Up-Down Notification) от элемента с идентификатором IDC_SPIN, то управление будет передано функции-обработчику OnDeltaposSpin. Теперь в конце файла найдите эту функцию:
void CLookDlg::OnDeltaposSpin(NMHDR *pNMHDR, LRESOLT *pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
// TODO: Add your control notification handler code here
*pResult = 0; }
Вот здесь происходит то, о чем было сказано выше. Указатель PNMHDR приводится к типу указателя на более сложную структуру NM_UPDOWN. Это нужно для того, чтобы достать из нее необходимую информацию. Теперь с помощью указателя pNMUpDown мы можем добыть требуемое приращение показаний счетчика (pNMUpDown->iDelta). Вместо комментария // TODO: вставьте следующий фрагмент кода:
//====== Вычисляем желаемую позицию
int nPos = m_Spin.GetPos() + pNMUpDown->iDelta;
//====== Если она вне допустимых пределов, то уходим
if (nPos < 0 || m_nltems <= nPos) return;
//====== Корректируем позицию ползунка
m_Slider.SetPos(nPos);
//====== Расшифровываем код ошибки
Getlnfo(nPos);
//====== Вызываем обмен данными с элементами окна диалога
UpdateData(FALSE);
Здесь уместно напомнить, что Studio.Net 7.0, как и Visual Studio 6, позволяет форматировать введенный текст так, как это принято в сообществе разработчиков. Выделите весь код функции и дайте команду Edit > Advanced > Format Selection или Alt+F8.
В коде мы используем данные (m_Spin, m_nltems, m_Slider) и метод (Getlnfо), которых еще нет в классе, но вы, наверное, имеете некоторый опыт программирования и знаете, что разработка часто идет в обратном порядке. Введем эти элементы в состав класса позже, а сейчас дадим оценку того, что только что сделали. С помощью ClassWizard мы ввели в класс главного окна обработку уведомляющего сообщения UDN_DELTAPOS, работающего по схеме WM_NOTIFY. Теперь введем обработку сообщения EN_CHANGE, поступающего от окна редактирования IDC_FIND каждый раз, когда в нем происходят изменения. Это сообщение работает по старой схеме и не влечет за собой необходимости преобразовывать указатели на структуры данных.

  1. Вновь откройте шаблон диалога и установите курсор мыши в окно IDC_FIND.
  2. В окне Properties нажмите кнопку с подсказкой ControlEvents.
  3. В появившемся списке уведомляющих сообщений, которые генерирует окно редактирования, выберите сообщение EN_CHANGE и его реализацию <Add>.

Проверьте результаты работы ClassWizard. Они должны быть видны в трех разных местах вашего приложения. В файле LookDlg.h должен появиться прототип функции обработки
void OnChangeFind (void) ;
в файле LookDlg.cpp должен появиться новый элемент карты сообщений
ON_EN_CHANGE(IDC_FIND, OnChangeFind)
и заготовка тела функции обработки, в которую мы должны внести свою функциональность:
void CLookDlg::OnChangeFind(void)
{
// TODO: Если это RICHEDIT control, то он не пошлет
// уведомления пока вы не дадите своей версии функции
// CDialog::OnInitDialog() и не сделаете вызов функции
// CRichEditCtrl().SetEventMask() с флагом ENM_CHANGE,
// включенным с помощью операции побитового ИЛИ.
// TODO: Здесь вставьте код обработки уведомления.
}
В комментариях CLassWizard предупреждает нас о том, что с элементом типа Rich Edit control надо работать по особым правилам. К нам это не относится, поэтому уберите комментарии и вставьте вместо них такой код:
CString s;
//==== Выбираем код ошибки, введенный пользователем
GetDlgltemText(IDC_FIND, s) ;
//==== Преобразуем к типу string, с которым мы работаем
string find = s;
//==== Ищем код в контейнере
m_Vector
for (int n=0;
n < m_nltems is find != m_Vector[n].Code;n++);
if (n < m_nltems) // Если нашли,
{
Getlnfo(n); // то расшифровываем этот код
m_Slider.SetPos(n); // и синхронизируем ползунок
UpdateData(FALSE); // Высвечиваем данные в окнах
}
Переменная s типа CString понадобилась для того, чтобы воспользоваться функцией GetDlgltemText, которая вычитывает содержимое окна редактирования. Приходится делать преобразование к типу string, так как мы хотим работать со стандартными строками (string) библиотеки STL.
Возвращаясь к элементам управления в окне диалога, отметим, что ползунок тоже посылает уведомляющие сообщения по схеме WM_NOTIFY. Их всего три и вы можете их увидеть в окне Properties после нажатия кнопки ControlEvents, если предварительно установите фокус на элементе IDC_SLIDER. Одно из них — NM_RELEASEDCAPTURE — подходит нам, так как посылается в тот момент, когда пользователь отпускает движок после его установки мышью в новое положение. Но мы не будем вводить реакцию на это уведомление, так как есть другое (старое) сообщение Windows — WM_HSCROLL (или WM_VSCROLL при вертикальном расположении ползунка), которое работает более эффективно. Дело в том, что ползунок управляется не только мышью. Если он обладает фокусом, то реагирует на все клавиши перемещения курсора (4 стрелки, Page Up, Page Down, Home, End). Это очень удобно, так как позволяет гибко управлять темпом перемещения по многочисленным кодам ошибок. Введите реакцию оконного класса на сообщение WM_HSCROLL.

  1. Вновь откройте шаблон диалога и установите фокус в его окне. Проследите, чтобы он не был ни в одном из элементов управления.
  2. В окне Properties нажмите кнопку Messages, найдите в списке сообщение WM_ HSCROLL и укажите действие <Add>.

Отыщите изменения в классе CLookDlg. Их должно быть три. Отметим, что когда ClassWizard делает вставки в карту сообщений, то он пользуется своим опознавательным знаком — знаком комментария вида //}} AFX_MSG_MAP. Напомним, что в
Visual Studio 6 эти знаки существовали парами, а вставки между элементами пар отличались цветом. Теперь все упростилось. Введите код в тело функции-обработчика так, чтобы она была:
void CLookDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
//====== Расшифровываем новый код
Getlnfo(m_Slider.GetPos());
//====== Помещаем данные в поля диалога
UpdateData(FALSE); }
Сообщение WM_HSCROLL посылается в те моменты, когда ползунок изменяет свое положение как с помощью мыши, так и с помощью клавиш. В обработчике мы выявляем новую позицию ползунка, ищем и расшифровываем код, соответствующий этой позиции. Обратите внимание на то, что мы не пытаемся синхронизировать счетчик. Когда приложение будет работать, вы увидите, что последний, тем не менее, отслеживает изменения позиции ползунка. Попробуйте самостоятельно найти объяснение этому факту. Ответ можно найти в MSDN по теме CSpinButtonCtrl, если обратить внимание на то, что счетчик может иметь (Buddy) двойника-приятеля, в качестве которого мы уже выбрали окно редактирования IDC_CURRENT.
  
Создание и связывание переменных
Итак, мы ввели в состав класса, управляющего главным окном приложения, способность реагировать на уведомляющие события UDN_DELTAPOS, EN_CHANGE, а также на событие Windows WM_HSCROLL. Теперь пора показать, как с помощью Studio.Net можно создать переменные, способные обмениваться данными с элементами управления (дочерними окнами) диалога. Технология обмена в Visual C++ давно устоялась и происходит по такому алгоритму:

  1. При открытии окна диалога ему посылается сообщение WM_INITDIALOG, на которое откликается либо класс вашего диалога, либо родительский класс CDialog.
  2. В функции отклика OnlnitDialog вашего класса вы делаете необходимые начальные установки и вызываете родительскую версию этой функции. Причем порядок этих действий зависит от типов элементов управления и их установок.
  3. Родительская версия OnlnitDialog вызывает функцию UpdateData (FALSE) для провоцирования обмена данными между переменными диалогового класса и элементами управления. Направление обмена задается параметром функции UpdateData: если он TRUE, то обмен совершается в сторону переменных, а если FALSE, то в сторону элементов управления. Очевидно, что в состав класса надо ввести переменные и связать их с элементами управления.
  4. После того или по мере того как пользователь ввел или вводит изменения в состояния элементов управления, вновь, если это предусмотрел программист, вызывается функция UpdateData (TRUE) или одна из функций типа GetDlgitem*. Они провоцируют обмен данными в сторону переменных.
  5. Функция UpdateData вызывает функцию DoDataExchange, которая обязательно есть в каждом диалоговом классе. Последняя состоит из последовательности вызовов глобальных функций MFC типа DDX_ и DDV_, которые умеют надежно обменивать данные в обе стороны и вежливо сообщать о возможных ошибках и несоответствиях при обмене.
  6. При закрытии диалога (здесь имеется в виду не наш диалог, который является главным окном приложения, а обычный модальный диалог) каркас приложения вновь вызывает UpdateData (TRUE) для того, чтобы произвести считывание данных, введенных пользователем.

Необходимо помнить, что простым элементам -управления (static Control, Button Control или Edit Control) обычно ставят в соответствие простые переменные типа int, BOOL или cstring. Более сложным элементам (Spin Control, Slider Control) обычно соответствуют переменные, которые являются объектами классов (CSpinButtonCtrl, CSliderCtrl). Сейчас мы введем в диалоговый класс переменные, которые мы свяжем (ассоциируем) с элементами управления. Некоторые из этих переменных мы уже заочно использовали в коде функций-обработчиков.

  1. Откройте окно диалога, установите фокус на счетчик (IDC_SPIN) и вызовите контекстное меню.
  2. В меню выберите команду Variable — появится мастер Add Variable Wizard.
  3. В окне мастера установите флажок Control variable.
  4. Переключатель Control-Value установите в положение Control.
  5. В окне Control ID выберите идентификатор элемента IDC_SPIN.
  6. В окне Variable Name задайте имя переменной m_ Spin.
  7. В окне Access выберите тип доступа private.
  8. В окне Comment задайте комментарий: Счетчик кодов.
  9. Нажмите кнопку Finish.

В окне Class View отыщите новый узел Variables, раскройте его и щелкните два раза мышью элемент m_Spin. В окне LookDlg.h вы увидите, что мастер вставил декларацию:
//====== Счетчик кодов
CSpinButtonCtrl m_Spin;
Найдите тело функции DoDataExchange и убедитесь, что в ней появилась строка:
DDX_Control (pDX, IDC_SPIN, m__Spin) ;
которая связывает элемент IDC_SPIN с объектом m_spin. Теперь повторите все действия для элемента IDC_SLIDER. В классе CLookDig должен появиться объект m_Slider класса CSliderCtrl.
В окне диалога осталось еще довольно много элементов управления, с которыми не связаны никакие переменные. Сейчас мы создадим эти переменные, но предварительно напомним, что элементы типа static Control (поле текста) могут быть как управляемыми, так и нет. В последнем случае все они должны иметь один и тот же идентификатор IDC_STATIC. Мы будем управлять шестью элементами типа static и одним элементом (IDC_CURRENT) типа Edit Control. Все элементы будут связаны с переменными по схеме Value, то есть между ними будет происходить обмен с помощью функций DDX_Text, а переменные будут иметь тип cstring. Процедура создания и связывания переменных для всех элементов типа static одинакова, поэтому мы приведем описание только одной, а вы повторите ее для всех других.

  1. Откройте окно диалога, установите курсор в окно IDC_TOTAL и вызовите контекстное меню.
  2. В меню выберите команду Variable — появится мастер Add Variable Wizard.
  3. В окне мастера Control ID: должен быть выбран идентификатор IDC_TOTAL.
  4. Установите флажок Control variable.
  5. Переключатель Control-Value установите в положение Value.
  6. В окне Access: выберите тип доступа private.
  7. В окне Variable Type: задайте тип переменной CString.
  8. В окне Variable Name: задайте имя переменной m_Total.
  9. Нажмите кнопку Finish.

Полезно просмотреть состав класса CLookDlg и убедиться в том, что в нем появилась новая переменная m_Total, а в тело DoDataExchange добавлена строка:
DDX_Text(pDX, IDCJTOTAL, mJTotal);
Вызов функции DDX_Text гарантирует, что в ключевые моменты жизни приложения будет производиться обмен между переменной m_Total и полем текста IDC_TOTAL. Вы никогда не будете явно вызывать функцию DoDataExchange. Ее вызывает функция UpdateData. Она создает объект вспомогательного класса CDataExchange и задает направление обмена. Если параметр функции UpdateData равен TRUE или отсутствует, то обмен осуществляется в сторону переменных, если он равен FALSE, то — наоборот. Каркас приложения без вашего участия и в нужные моменты вызывает UpdateData, но вы можете и сами вызывать эту функцию тогда, когда необходимо произвести обмен. Обычно это моменты, когда вам надо считать все данные из окон или, наоборот, отразить в окнах изменения в данных, произведенные программой. Сейчас повторите шаги по созданию переменных, связанных с окнами элементов типа static Control. Все переменные должны иметь тип cstring. Данные для этой операции приведены в табл. 4.4.
Таблица. 4.4. Идентификаторы элементов и связанные с ними переменные

Если не было ошибок ввода, то в теле функции DoDataExchange должно быть 6 строк вида DDX_Text. Процедура по созданию и связыванию переменных для окон редактирования почти не отличается от только что рассмотренной (для текстовых полей). Различия вы увидите в списке по выбору типа переменной (Variable Туре). Для элементов типа Edit Control существует множество преобразований вводимого текста в данные числовых типов (int, double и т. д.). С учетом сказанного создайте переменную cstring m_CurPos и свяжите ее с полем редактирования IDC_CURRENT.
  
Вставка значка
Если вы вновь посмотрите на окно диалога (рис. 4.1), то увидите справа два элемента типа Picture Control с идентификаторами IDC_RIGHT и IDC_LEFT. Эти элементы необходимо связать с растровыми изображениями значков (ресурсы типа Icon). Так как приложение выполняет функции браузера, то сюда я намереваюсь вставить изображения глаз, которые в принципе можно создать средствами графического редактора Studio.Net. Однако более простым выходом является использование изображений, которые были созданы мастерами своего дела и уже существуют в bmp-файлах. Достаточно много изображений входит в стандартную поставку студии. Они расположены в нескольких папках по адресу ...\Microsoft Visual Studio.Net\Common7\Graphics\icons. Вот алгоритм связывания элемента типа Picture Control с растровым изображением.

  1. Дайте команду Project Add > Resource (или нажмите Ctrl+R).
  2. В окне диалога Add Resource выберите тип ресурса Icon и нажмите кнопку New.
  3. Откройте файл с каким-либо существующим изображением File > Open и т. д., но не забудьте изменить фильтр поиска на (*.*).
  4. Скопируйте найденное и открытое в рамках студии изображение в буфер (Ctrl+C).
  5. Перейдите в предыдущее окно с пустым изображением (Ctrl+F6) и вставьте его (Ctrl+V).
  6. В окне Properties измените идентификатор на тот, который был задан в качестве свойства Image для элемента Picture Control. В нашем случае это IDI_EYERIGHT или IDI_EYELEFT.
  7. Закройте ненужные окна.

Пользуясь этим алгоритмом, создайте две новые картинки и свяжите их с элементами IDC RIGHT И IDC LEFT.
Примечание
Если вы найдете изображение одного глаза (скажем, левого) и откроете его в рамках студии, то изображение можно скопировать в новый ресурс типа Icon и перевернуть (сделать глаз правым), дав команду Image > Flip Horizontal. Исследуйте и другие команды этого меню.
Элементы управления типа Picture Control можно сделать «чувствительными». Покажем, как ввести в приложение способность реагировать на нажатие кнопки мыши в области, занимаемой нашими изображениями глаз. По схеме, которую вы использовали, когда вводили в класс диалога реакцию на WM_HSCROLL, создайте функцию — обработчик сообщения WM_LBUTTONDOWN (нажата левая кнопка мыши). В тело заготовки для функции-обработчика внесите следующий код:
void CLookDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect left, right;
//====== Узнаем координаты левой картинки
GetDlgItem{IDC_LEFT)->GetWindowRect(Sleft);
//====== Переход к относительным координатам
ScreenToClient(Sleft);
//====== Узнаем координаты правой картинки
GetDlgItem(IDC_RIGHT)->GetWindowRect(Sright) ;
ScreenToClient(bright);
//====== Объединяем площади двух картинок
left.UnionRect(left,right);
//====== Если координаты курсора внутри этой площади
if (left.PtlnRect(point))
//======Вызываем диалог About
OnSysCommand(IDM_ABOUTBOX,0);
//====== Вызов родительской версии CDialog::OnLButtonDown(nFlags, point);
}

Диалог About


При нажатии кнопки в области картинок мы вызываем диалог About, от которого отказались при создании проекта. Цель такого поступка — самостоятельно создать диалог, поместить в него растровое изображение и ввести команду для его запуска в меню управления (Control menu) главного окна.
Создайте новый диалог (Project > Add Resource > Dialog > New), удалите из его окна кнопку Cancel, разместите в нем Static Control, Group Box и Picture Control.
Для картинки установите свойства: Type: Icon, ID: IDC_EYE, Image: IDI_EYELEFT. Обратите внимание на то, что свойство Image недоступно, пока вы не установите тип изображения Icon. Для окна диалога свойство ID задайте равным IDD_ABOUTBOX. В класс CLookDlg введите обработчик сообщения WM_SYSCOMMAND. Каркас приложения вызывает обработчик этого сообщения в те моменты, когда пользователь выбирает команды из меню управления или когда он пользуется кнопками свора-чивания окна:
void CLookDlg::OnSysCommand(UINT nID, LPARAM IParam)
{
if ((nID & OxFFFO) == IDM_ABOUTBOX)
CDialog(IDD_ABOUTBOX).DoModal();
else
CDialog::OnSysCommand(nID, IParam);
}
Здесь, как видно из кода, мы проверяем идентификатор команды, и если он соответствует команде About, то запускаем диалог в модальном режиме. Теперь необходимо вставить в меню управления окном команду About. Отметьте, что это меню создает каркас приложения и оно не имеет соответствующего ресурса в нашем приложении. Поэтому управление меню производится методами класса смени. Обычно это делают в функции OnlnitDialog. В этой же функции производят инициализацию элементов управления. Внесите в нее следующие изменения:
BOOL CLookDlg::OnlnitDialog()
{
//======= Добываем адрес меню управления окном
CMenu* pSysMenu = GetSystemMenu(FALSE) ;
if (pSysMenu)
{
//====== Добавляем команду About
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, "About...");
}
//====== Загружаем свой (нестандартный) значок
HICON hMylcon = ::Loadlcon(GetModuleHandle(0),(char*)(IDI_EYELEFT)); Setlcon(hMylcon, TRUE);
// Set big icon Setlcon(hMylcon, FALSE);
// Set small icon
//====== Если не удалось найти файл,
if (IReadErrors () }
{
PostMessage(WM_QUIT); // уходим
return FALSE;
}
//====== Количество элементов в контейнере
//=====преобразуем в строку
m_Total.Format("%d",m_nltems);
//====== Ищем и расшифровываем первый код ошибки
Getlnfo(0);
//====== Вызов родительской версии диалога
CDialog::OnlnitDialog ();
//====== Устанавливаем окно-двойник для счетчика
m_Spin.SetBuddy(GetDlgItem(IDC_CURRENT));
//====== Диапазон изменения показаний счетчика
m_Spin.SetRange(0, m_nlterns-1);
//===== Диапазон изменения позиции ползунка
m_Slider.SetRange(0, m_nlteras-l);
//===== Устанавливаем цену делений для шкалы ползунка m_Slider.SetTicFreq(m_nltems/10);
return TRUE;
}
Здесь показаны методы начальной установки показаний счетчика и позиции ползунка. Кроме того, мы сменили значок для окна приложения. Теперь это не IDI_WINLOGO, а наш глаз. Команда About добавляется в меню управления окном с помощью метода AppendMenu. Чтобы проверить правильность некоторых изменений, надо запустить приложение, но сначала надо ввести в состав ресурсов приложения идентификатор команды меню IDM_ABOUTBOX и временно исключить те фрагменты кода, которые еще не могут работать. Для задания нового идентификатора:

  1. Вызовите контекстное меню в окне Resource View и выберите команду Resource Symbols.
  2. В окне появившегося диалога нажмите кнопку New.
  3. В окне Name: следующего диалога введите IDM_ABOUTBOX и нажмите ОК.

Полезным упражнением будет временное исключение (с помощью комментариев) того кода, который пока не может функционировать. Добейтесь того, чтобы код компилировался без ошибок. Затем можно запустить приложение и проверить работу меню и диалога About. Он должен вызываться как из меню, так и с помощью щелчка по картинкам. Скорее всего, он работать не будет. Я намеренно завел вас в эту ловушку, так как сам в нее попадался. Объяснение отказа можно найти в справке по функции OnSysCommand. Там сказано, что четыре младших бита параметра nio, который определяет идентификатор команды меню, используются Windows, поэтому в командах пользователя их следует обнулять. Мы это делаем путем побитового логического умножения на константу 0xFFF0:
if ((nID & 0xFFF0) == IDM_ABOUTBOX) CDialog(IDD_ABOUTBOX).DoModaK);
Но числовое значение идентификатора IDM_ABOUTBOX, которое было определено студией в диалоге Resource Symbols, скорее всего, не удовлетворяет этому условию и запуск диалога не производится. Чтобы изменить значение идентификатора, надо вновь открыть диалог Resource Symbols, найти IDM_ABOUTBOX в списке идентификаторов и изменить его значение, например на 112. Число должно быть больше того, которое предлагает студия, и делиться на 16, так как 4 младших бита должны быть нулями. После изменений такого рода полезно дать команду Build > Rebuild Solution и вновь запустить приложение. Если вы во всем разобрались, то диалог About должен работать.
Примечание
Если вы не знаете, что такое разделитель команд меню (Menu Separator), то закомментируйте строку, вставляющую его и, запустив приложение, сравните облик меню с тем, который был до этого.

Внесение логики разработчика


Итак, мы покончили с интерфейсной шелухой и нам осталась самая интересная и трудная часть работы — внесение в приложение той логики, которая была определена на этапе постановки задачи. Она состоит в следующем. Мы должны найти файл Win Error, h, просканировать его и выудить из него полезную информацию. Эту информацию надо поместить в контейнер объектов типа ErrorType. Если вы помните, мы поместили объявление этого типа в начало файла LookDlg.h. Там же находится определение нового типа ERROR_VECTOR — контейнера структур ErrorType. Теперь настала пора ввести в класс главного окна сам контейнер и его размерность. Довольно часто в целях экономии времени переменные вводят в состав класса вручную, то есть без помощи инструментов Studio.Net. Сейчас мы так и поступим. В секцию private класса CLookDlg введите следующую декларацию:
//====== Контейнер структур типа ErrorType
ERROR_VECTOR m_Vector;
//====== Размерность контейнера
int m_nltems;
  

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика