Анализатор кодов ошибок
Приложение на основе диалога
Дизайн диалога
Управление окном Toolbox
Реакция окна на уведомляющие сообщения
Создание и связывание переменных
Вставка значка
Диалоговое окно About
Внесение логики разработчика
Собственные методы класса
Поиск в реестре
Синтаксический анализ файла
Диалог About При нажатии кнопки
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 и временно исключить те фрагменты кода, которые еще не могут работать. Для задания нового идентификатора:
Вызовите контекстное меню в окне Resource View и выберите команду Resource Symbols.
В окне появившегося диалога нажмите кнопку New.
В окне 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), то закомментируйте строку, вставляющую его и, запустив приложение, сравните облик меню с тем, который был до этого.
Дизайн диалога Развитие диалогового
Окно диалога для поиска элементов управления
Команда Rename > Tab из контекстного меню заголовка вставки позволяет переименовать всю вставку, а команда Rename > Item из контекстного меню самой вставки позволяет переименовать элемент. Команда Show > All > Tabs работает по принципу переключателя. Команда List > View, работающая по этому же принципу, позволяет переключать режим просмотра инструментов (значки/список). Команда Sort > Items > Alphabetically из контекстного меню заголовка вставки позволяет отсортировать инструменты, а команды Move > Up или Move > Down из меню окна — переместить их.
Поиск в реестре Алгоритм поиска
ok = ::RegOpenKeyEx (HKEY_LOCAL_MACHINE, vs, 0,
KEY_READ, Shkey)== ERROR_SUCCESS && ::RegQueryValueEx (hkey,"ProductDir", 0, Sdw, (LPBYTE)path, &d) == ERROR_SUCCESS;
string sPath = "";
//====== Дополняем путь именем папки и файла
if (ok)
sPath = CString(path) + "\\Include\\Winerror.h";
return sPath; }
Поиск в реестре производится с помощью API-функций RegOpenKeyEx и RegQueryValueEx, первая из которых ищет и открывает ключ, заданный текстовой строкой, а вторая — ищет значение (value), связанное с открытым ключом. Результатом первой операции является Windows-описатель открытого ключа, который помещается по адресу hkey. Вторая операция требует задать hkey и имя искомого значения (в нашем случае — это ProductDir), а ее результатом является собственно значение (в нашем случае — полный путь к папке, где расположена Studio.Net). Если имя пусто, то функция возвращает значение по умолчанию (default).
Файл WinError.h расположен в папке Include, вложенной в папку Studio.Net. Поэтому мы дополняем найденный путь именем папки и файла. В случае неудачи при поиске папки наше приложение должно вести себя разумно, и поэтому мы предлагаем пользователю самому отыскать местоположение папки, где установлена студия. Это делается путем создания и вызова специального диалога по поиску папки. Диалог использует API-функции из группы Shell-API — подмножества API, которое поддерживается Shell32.dll и использует объекты COM (Component Object Model). Для успешной работы оболочки (shell) необходимо получить указатель на интерфейс (термин из COM) iMalloc, с помощью которого производится динамическое управление памятью. С технологией СОМ мы познакомимся позже, а сейчас введите в состав оконного класса еще одну функцию.
string CLookDlg::GetPathFromUser(void)
{
//====== Путь к файлу WinError.h пока пуст
string path = "";
//====== Указатель на интерфейс IMalloc
LPMALLOC pMalloc;
if (MessageBox("He могу найти папку,"
" где расположена Studio.Net"
" \r\rBu покажете путь к ней?",
"Поиск в реестре Windows",
MB_YES NO | MB_ICONQUESTION)==IDNO
FAILED( SHGetMalloc(&pMallo))
return path;
BROWSEINFO bi;
ZeroMemory (Sbi,sizeof(bi));
bi.ulFlags = BIF_RETURNONLYFSDIRS;
//====== Запуск диалога поиска папки
LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
if (pidl) {
TCHAR szDir[MAX_PATH];
if (SHGetPathFromlDList(pidl,szDir))
{
path = szDir;
path += "\\Include\\Winerror.h"; }
pMalloc->Free(pidl); pMalloc->Release();
}
return path;
}
Попытка активизировать СОМ-объект и получить указатель на его интерфейс производится путем вызова функции SHGetMalloc. Первые две буквы SH означают принадлежность функции к семейству Shell-API. Макрос FAILED() проверяет на отрицательность возвращаемый функцией результат типа HRESULT, тем самым определяя факт неудачи. Структура BROWSEINFO помогает управлять параметрами диалога по поиску папки.
Для многих стандартных диалогов типично использование специальных структур, их обслуживающих. Вспомните диалог по выбору цвета. Там использовалась структура типа CHOOSECOLOR. Диалог по поиску и замене текста обслуживается структурой FINDREPLACE. Диалог по выбору шрифта работает со структурой CHOOSEFONT. Здесь мы используем структуру BROWSEINFO.
Функция SHBrowseForFolder запускает диалог, который позволяет пользователю выбрать папку. Она возвращает адрес списка идентификаторов pidl (pointer to identifier list), описывающих местоположение выбранной папки по отношению к корню (root) пространства имен (namespace). По умолчанию namespace — это рабочий стол (desktop). При работе с элементами СОМ важно помнить, что после использования интерфейса мы обязаны освободить его ресурсы вызовом метода Free и скорректировать (уменьшить на единицу) счетчик числа его пользователей (Release). Функция SHGetPathFromlDList преобразует список идентификаторов в системный файловый путь (szDir), который мы копируем в строку path.
Приложение на основе диалога Уверен
ERROR_ACCESS_DENIED 5L
Надеюсь, вы не забыли, что суффикс ' L' задает тип константы (long). Вторая категория — это коды ошибок, возвращаемых многими OLE- и СОМ-АР1-функци-ями, например:
#define E_NOTIMPL _HRESULT_TYPEDEF_(Ox80004001L)
Последние имеют вид шестнадцатеричных чисел, которые хранятся в переменных типа HRESULT.
// 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 Формат кода 1Л/1п32-ошибок
31-30 |
29 |
28 |
27-16 |
15-0 | |||||||||
Severity |
С |
R |
Facility |
Code | |||||||||
Таблица. 4.2 Формат HRESULT СОМ-ошибок | |||||||||||||
31 |
30 |
29 |
28 |
27 |
15-0 | 26-16 | |||||||
S |
R1 |
С1 |
N |
r |
Code | Facility | |||||||
Символы имеют следующий смысл:
с — 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. В левой части своего окна он имеет список команд, которые играют роль вкладок, раскрывающих различные окна-страницы правой части окна, поэтому команды слева мы будем называть вкладками, а окна справа — страницами. Для создания заготовки:
В меню File t New выберите команду Project.
В появившемся диалоге New Project, в окне Project Type раскройте ветвь дерева под именем Visual C++ Projects и выберите ветвь Win32 Projects.
В окне Templates выберите тип проекта: Win32 Project.
В окне Name задайте имя проекта: Look. В окне Location задайте или оставьте без изменения местоположение новой папки с файлами решения (solution).
В окне мастера MFC Application Wizard выберите вкладку Application Type и в окне справа укажите тип MFC-приложения — Dialog based.
Выберите вкладку User Interface Features и введите заголовок окна диалога — Look for an Error Code, так как английский язык в ресурсах работает значительно надежней русского, по крайней мере в бета-версии Studio.Net 7,0. Снимите флажок About — это упростит стартовое приложение.
Перейдите на страницу Advanced Features и снимите флажок ActiveX Controls.
Проанализируйте содержимое страницы Generated Classes. Здесь вы можете изменить имена двух классов, которые создаст мастер, но лучше этого не делать, так как имена составляются по стандартной схеме, которая упрощает обмен идеями в сообществе разработчиков.
Нажмите кнопку 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:
Перейдите в окно Resource View, раскройте дерево ресурсов и дважды щелкните на идентификаторе диалога IDD_LOOK_DIALOG.
Откройте окно Properties, в разделе Appearance найдите свойство MinimizeBox и измените его значение на TRUE.
В окне Resource View, раскройте узел Icon, выберите идентификатор значка IDR_ MAINFRAME и нажмите клавишу Delete.
В окне редактора кодов (LookDlg.cpp) целиком удалите тела двух функций OnPaint, OnQueryDraglcon, два элемента карты сообщений: ON_WM_PAINT и ON_WM_QUERYDRAGICON и строку вызова Loadlcon из тела конструктора класса.
В файле 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
Запустите приложение и убедитесь, что окно диалога теперь сворачивается и значок на месте.
Реакция окна на уведомляющие сообщения
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 реакции на уведомляющие сообщения:
Откройте шаблон диалога и установите курсор мыши на счетчике (IDC_SPIN).
В окне Properties нажмите кнопку с подсказкой ControlEvents.
В появившемся списке уведомляющих сообщений, которые генерирует счетчик, выберите 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 каждый раз, когда в нем происходят изменения. Это сообщение работает по старой схеме и не влечет за собой необходимости преобразовывать указатели на структуры данных.
Вновь откройте шаблон диалога и установите курсор мыши в окно IDC_FIND.
В окне Properties нажмите кнопку с подсказкой ControlEvents.
В появившемся списке уведомляющих сообщений, которые генерирует окно редактирования, выберите сообщение 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.
Вновь откройте шаблон диалога и установите фокус в его окне. Проследите, чтобы он не был ни в одном из элементов управления.
В окне 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.
Синтаксический анализ файла До
GetNextErrorCode(ifstreams is)
{
//===== Поиск и выбор очередной ошибки из потока is
string s;
//==== Ищем строку текста "Messageld:"
int pos = FindText(is, s, "Messageld: ");
//==== Если дошли до конца файла, уходим if (is.eofO)
return false;
//=== Индекс ошибки следует за строкой "Messageld:
gsID = s.substr(pos);
//=== Ищем строку текста "MessageText: "
FindText(is, s, "MessageText:");
// Текстовое описание ошибки следует за пустым текстом
FindText(is, gsMsg="");
// Код ошибки (или HRESULT) следует за #define
FindText(is, s, "tdefine");
//== Ищем 'L' и стираем его и все, что за ним следует s.erase(pos=s.гfind("L"));
//======= Ищем пробел слева от кода
gCode = s.substr(s.rfind(" ",pos)+l);
//=== Ищем скобку, которая предшествует СОМ-ошибкам
if ( (pos=gCode.rfind("(")) != -1)
gCode.erase(0,pos+l); // Усекаем строку слева
return true;
}
Файл WinError.h не был введен вручную. Он сгенерирован специальной программой и поэтому имеет регулярную, стабильную структуру, специфику которой мы используем"для проведения синтаксического анализа и поиска нужной информации. Вы можете вновь обратиться к структуре текста, описывающего ошибку. Она приведена в начале главы. Использование заранее известных признаков, выделяющих искомый текст из всей массы символов, является характерным приемом при проведении синтаксического анализа текста. Так, мы знаем, что идентификатор ошибки следует за строкой «Messageld:» (с учетом пробела), поэтому мы ищем позицию конца этой строки и выбираем подстроку (substring) начиная с этой позиции:
gsID = s.substr(pos);
Алгоритм поиска строки текста реализован в функции FindText, которая еще не создана, но уже сейчас предъявлены к ней требования. Мы хотим, чтобы она построчно считывала файл и работала в двух режимах:
поиск позиции конца строки, заданной в параметре;
поиск непустой строки текста и выбор ее в переменную, переданную параметром.
Пустой мы считаем строку, которая содержит только символы комментария и/ или символы пробела. Также известно, что текстовое описание ошибки следует за строкой «MessageText:». Поэтому после нахождения этой строки поиск запускается во втором режиме, когда FindText пропускает пробелы и символы комментария «//» и читает текст, следующий за ними. Здесь важно отметить, что некоторые сообщения занимают несколько строк комментария, следовательно, надо выбрать все строки и слить их в одну, разделив пробелом.
Далее мы выделяем код ошибки с учетом особенностей, связанных с тем, что существуют два различающихся между собой формата: Win32-onni6oK и СОМ-ошибок. Все коды СОМ-ошибок стоят в скобках, имеют суффикс' L ' и префикс _HRESULT_TYPEDEF_. Зная этот факт, мы проводим синтаксический анализ так, чтобы выделить числовой код. Осталось написать код функции FindText, требования к которой уже сформулированы. Она должна получать в параметрах:
ссылку на объект, управляющий потоком ввода, связанным с файлом;
ссылку на строку типа string, в которую помещается результат;
адрес строки, которую надо найти.
Если последний параметр задать по умолчанию, то функцию можно использовать в двух режимах. Вставьте тело этой глобальной функции в начало файла LookDlg.cpp (после объявления массива gsFacilities):
int FindText (ifstreams is, strings s, TCHAR *text=NULL)
{
//=== Ищет подстроку <text> или первую непустую строку
//=== Цикл прохода по всем строкам файла
for (int pos=-l; pos==-l && !is.eof(); )
{
//====== Считываем всю строку (до символа'\n')
getline (is, s, '\n');
//====== В первом режиме text не равен нулю
//====== и мы ищем этот текст, иначе ищем
//====== первый непустой символ
pos = text ? s.find(text) : s.find_first_not_of("/ ");
if (pos!=-l) // Если нашли
s.erase(0,pos); // Усекаем строку слева
} ;
// Если искали и нашли текст, то возвращаем его длину
// которая имеет смысл позиции, следующей за текстом
if (text && ! is.eof () )
return strlen (text) ;
// Если ищем непустую строку, то пропускаем все пустые
string st;
for (pos=0; pos!=-l && !is.eof(); )
{
getline(is,st,'\n');
pos = st.find_first_not_of("/ ");
//====== Если нашли непустой текст,
//====== то сливаем его с уже найденным
if (pos != -1)
s += ' ' + st.substr(pos);
}
// Возвращаем 0-ю позицию, так как нужен весь текст
return 0;
}
В такие моменты, когда программа почти готова, важно остановиться и вспомнить, не забыли ли мы выключить газ и свет. Я имею в виду освободить память, занимаемую какими-либо динамическими структурами. Мы используем контейнер структур, который необходимо где-то освобождать. Введите в public-секцию класса CLookDlg (в h-файле) деструктор этого класса:
~CLookDlgO { m_Vector.clear(); }
Это делать не обязательно, так как деструктор вызывается при выходе из приложения уже после того, как окно исчезло с экрана. Windows, закрывая процесс, освободит всю память, как в стеке, так и в heap, но правила хорошего тона говорят, что за собой надо убирать. Другим моментом, связанным с забывчивостью, является отсутствие директив #include для файлов заголовков используемых библиотек. Вы помните, что это следует делать в файле stdafx.h, чтобы пользоваться преимуществами pch-файла, который ускоряет повторные компиляции. Вставьте в конец файла stdafx.h следующие строки:
#include <iostream> // Потоковый ввод-вывод STL
//=== Буферизованные потоки, связанные с файлами (STL)
#include <fstream>
#include <string> // Текстовые строки STL
#include <vector> // Контейнеры STL типа vector
//====== Работаем в пространстве имен std
using namespace std;
Запустите программу, устраните возможные синтаксические и семантические ошибки и хорошо протестируйте. Подведем итог:
мы узнали о двух форматах данных типа HRESULT, которые используются для хранения информации о результате выполнения каких-либо операций;
потренировались в использовании контейнера STL и некоторых из его алгоритмов;
научились работать с объектами класса string, определенного в STL;
узнали, как с помощью инструментов студии создаются диалог, переменные диалогового класса (связываемые с элементами управления диалога), а также функции обмена данными с этими полями;
познакомились с уведомляющими сообщениями, работающими по схеме WM_NOTIFY, принятой для многих новых элементов управления;
узнали, как можно синхронизировать работу нескольких элементов управления;
применили объекты классов потокового ввода-вывода для чтения файла с данными о кодах ошибок;
научились производить поиск необходимой информации в реестре Windows;
познакомились с простыми приемами синтаксического анализа текста с известной структурой.
Создаем диалог Важным моментом
Идентификаторы элементов управления диалога
Тип элемента |
Заголовок (комментарий) |
Идентификатор | |||
Dialog |
WinError View |
IDD_LOOK_DIALOG | |||
Group-box |
Error Number: |
IDC_STATIC | |||
Spin |
IDC_SPIN | ||||
Edit |
// справа от IDC_SPIN |
IDC_CURRENT | |||
Slider |
IDC_SLIDER |
Text |
Total: |
IDC_STATIC | |||
Text |
// под Total: |
IDCJTOTAL | |||
Button |
Close |
IDCANCEL | |||
Group-box |
Parameters: |
IDC_STATIC | |||
Text |
Error Code: |
IDC_STATIC | |||
Text |
// справа от Error Code: |
IDC_CODE | |||
Text |
Find: |
IDC_STATIC | |||
Edit |
// справа от Find: |
IDC_FIND | |||
Picture |
IDC_RIGHT | ||||
Picture |
IDCJ.EFT | ||||
Text |
Severity: |
IDC_STATIC | |||
Text |
// справа от Severity: |
IDC_SEVERITY | |||
Text |
Facility: |
IDC_STATIC | |||
Text |
// справа от Facility: |
IDC_FACILITY | |||
Text |
Identifier: |
IDC_STATIC | |||
Text |
// справа от Identifier: |
IDCJD | |||
Text |
Message: |
IDC_STATIC | |||
Text |
// справа от Message: |
IDC_MSG |
Проверьте особые свойства элементов, которые должны быть такими, как показано ниже. Если они другие, то введите коррективы:
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.
Создание и связывание переменных
Идентификаторы элементов и связанные с ними переменные
Var |
m_Code |
m_ID |
m_Msg |
m_Severity |
m_FacHity | ||||||
ID |
IDC_CODE |
IDCJD |
IDC_MSG |
IDC_SEVERITY |
IDC_FACILITY |
Если не было ошибок ввода, то в теле функции DoDataExchange должно быть 6 строк вида DDX_Text. Процедура по созданию и связыванию переменных для окон редактирования почти не отличается от только что рассмотренной (для текстовых полей). Различия вы увидите в списке по выбору типа переменной (Variable Туре). Для элементов типа Edit Control существует множество преобразований вводимого текста в данные числовых типов (int, double и т. д.). С учетом сказанного создайте переменную cstring m_CurPos и свяжите ее с полем редактирования IDC_CURRENT.
Внесение логики разработчика Итак
CLookDlg: :ReadErrors ()
{
//==== Поиск и чтение информации об ошибках
//==== Пытаемся найти путь в реестре
string sPath = GetPathFromRegistry ( ) ;
//==== В случае неудачи пытаемся узнать у пользователя
if (sPath. empty () )
sPath = GetPathFromUser О ; if (sPath.emptyO)
return false; // При отказе уходим
//==== Пытаемся открыть файл
if stream is (sPath. c_str () ) ;
if (!is) {
MessageBox ("He могу найти WinError.h", "Выход") ;
return false;
//====== Последовательно ищем все ошибки
while (GetNextErrorCode (is) )
{
//==== Создаем новый объект типа ErrorType и
//==== помещаем его в контейнер
m_Vector.push_back (ErrorType (gCode, gsID, gsMsg) ) ;
}
is. closet);
// Закрываем файл
//====== Запоминаем размер контейнера
m_nltems = m_Vector . size () ;
return bool (m_nltems != 0) ;
}
Здесь мы вызываем функции (Getxxx), которых еще нет. Это является типичной практикой разработки многомодульных приложений. Мы определяем прототипы функций так, как того требует логика алгоритма, а тела будут созданы позже. Вы должны обратить внимание на объявление объекта is класса ifstream, который определен в STL. Поставьте курсор на имя класса ifstream и воспользуйтесь окном Dynamic Help, для того чтобы получить справку об этом классе. Из нее вы мало что узнаете, так как, к сожалению, все справки по библиотеке STL в разделе Reference слишком краткие, но если использовать поиск, то в MSDN можно получить достаточно подробную информацию о потоковом вводе-выводе.
В рассматриваемом коде мы вызываем конструктор класса ifstream, который создает поток ввода, связывает его с буфером и пытается открыть файл, путь к которому задан в параметре (sPath.c_str()). Вы помните, что вызов c_str() дает возможность пользоваться строкой в стиле языка с (то есть const char*), которая прячется в строке типа string. Операция "!", переопределенная в классе ifstream, сообщает нам о неудаче при открытии файла. Переменные gCode, gsio, gsMsg — это глобальные переменные, которые мы собираемся завести для временного хранения параметров ошибки (кода, индекса и сообщения).
Работая с объектами классов, вы можете создавать глобальные данные и функции. Это оправдано, когда надо расширить область видимости каких-то данных или когда функция имеет универсальный характер, например ищет в файле строку текста. Если вы знаете или вам кажется, что создаваемую функцию можно использовать и вне контекста класса, то ее целесообразно объявить глобальной.
В начало файла LookDlg.cpp (после директивы #endif) введите определения глобальных данных, которые будут использованы как в методах класса, так и в глобальных функциях:
//=== Текущие значения кода, индекса и текста сообщения
string gCode, gsID, gsMsg;
//====== Количество категорий (групп) ошибок
const int N_FACILITIES = 23;
//====== Имена категорий ошибок
TCHAR *gsFacilities[N_FACILITIES + 1] = {
"NULL", "RFC", "Dispatch",
"Storage", "Interface", "Unknown",
"Unknown", "Win32", "Windows",
"SSPI", "Control", "Cert",
"Internet", "MediaServer", "MSMQ",
"SetupAPI", "Smart Card", "COM+",
"AAF", "URT", "ACS",
"DPlay", "UMI", "SXS" };
Категории ошибок принято обозначать аббревиатурами, смысл которых можно выяснить в разделе Glossary MSDN. Например, аббревиатура RFC (Remote Procedure Call) обозначает категорию ошибок, связанных с обращением к процедурам, которые размещены на других процессорах сети.
Повторите последовательность действий по введению в класс вспомогательной функции и создайте функцию Getlnfo. Она выбирает из контейнера структуру, которая соответствует ошибке с индексом nPos, и присваивает переменным, связанным с элементами управления в окне диалога, значения, которые характеризуют ошибку (атрибуты ошибки). После такой операции можно проводить обмен данными (UpdateData(FALSE)) с дочерними окнами диалога и они «высветят» ошибку.
Переведите фокус мыши на узел CLookDlg в дереве классов Class View, вызовите контекстное меню и дайте команду Add > Add Function.
В окне мастера Add Member Function Wizard заполните следующие поля: Return type: void, Function name: Getlnfo, Parameter type: int, Parameter name: nPos.
Нажмите кнопку Add.
В поле Access: задайте тип доступа public:
void CLookDlg::GetInfo(int nPos)
{
// ======= Текущая позиция
m_CurPos.Format("%d",nPos);
if (nPos >= m_nltems)
return;
//======= Выбираем поля структуры
m_Code = m_Vector[nPos].Code.c_str();
m_Msg = m_Vector[nPos].Message.c_str() ;
m_ID= m_Vector[nPos].Identifier.c_str();
//====== Преобразование кода в целое число
DWORD dw = strtoul(LPCTSTR(m_Code),0,0);
//====== Выделяем старший бит (Severity)
m_Severity = dw & 0x80000000 ? "Fail" : "Success";
//=== СОМ-коды это НЕХ-коды, длина которых > 8 символов
//=== В этой ветви мы обрабатываем Win32-ошибки
if (m_Code.GetLength() < 8)
{
if (dw)
{
//====== Вставляем поля facility и severity
dw = 0x80000000 | (0x7 << 16) | (dw f, OxFFFF) ;
m_Severity = "Error";
}
}
//====== Выделяем поле facility
UINT f = (dw»16) & 0xlFFF;
//====== Выбираем нужную аббревиатуру
m_Facility = f <= N_FACILITIES |gsFacilities[f) : "Unknown";
}
Так как коды \Ут32-ошибок не имеют полей facility и severity (эти атрибуты появились позже), то их надо синтезировать. Таким же образом поступает макроподстановка HRESULT_FROM_wiN32, и ее можно использовать в этом месте, но мы (с учебной целью) вставили ее код. Если вы хотите опробовать макрос, то замените строку
dw = 0x80000000 | (0x7 << 16) | (dw & 0xFFFF);
на
dw = HRESULT_FROM_WIN32(dw);
Далее мы выделяем поле facility и выбираем из массива gsFacilities аббревиатуру, которая более информативна, чем число f, кодирующее facility.
Вставка значка Если вы вновь посмотрите
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);
}