Импортирование функций
Когда вы используете статическую компоновку, то включаете в файл проекта приложения соответствующий lib-файл, содержащий нужную вам библиотеку объектных модулей. Такая библиотека содержит исполняемый код модулей, который на этапе статической компоновки включается в exe-файл загрузочного модуля.
Если используется динамическая компоновка, в загрузочный exe-файл приложения записывается не исполнимый код функций, а ссылка на соответствующую DLL-библиотеку и функцию внутри нее. Как мы уже говорили, эта ссылка может быть организована с использованием либо имени функции, либо ее порядкового номера в DLL-библиотеке.
Откуда при компоновке приложения редактор связей узнает имя DLL-библиотеки, имя или порядковый номер экспортируемой функции? Для динамической компоновки функции из DLL-библиотеки можно использовать различные способы.
Инициализация локальной области данных в заданном сегменте
Для создания и инициализации локальной области данных в заданном сегменте вы можете воспользоваться функцией LocalInit :
BOOL WINAPI LocalInit(UINT uSegment, UINT uStartAddr, UINT uEndAddr);
Параметр uSegment перед вызовом функции должен содержать идентификатор сегмента, который будет содержать локальную область данных.
Параметр uStartAddr определяет начальный адрес локальной области данных, а параметр uEndAddr - конечный адрес локальной области данных.
Первые 16 байт в сегменте данных необходимо зарезервировать для системы.
Использование оператора IMPORTS
Использование библиотек импорта - удобный, но не единственный способ компоновки функций DLL-библиотеки. Этот способ можно рассматривать как способ неявной компоновки на этапе редактирования загрузочного модуля приложения, так как имена импортируемых функций нигде не перечисляются.
Другой способ основан на использовании в файле определения модуля приложения оператора IMPORTS . С помощью этого оператора можно перечислить импортируемые функции, указав имена функций или их порядковые номера , а также имена файлов соответствующих DLL-библиотек:
IMPORTS Msg=dllsrc.4 dllsrc.TellMe
Во второй строке приведенного выше примера приложение импортирует функцию Msg из DLL-библиотеки dllsrc.dll, причем порядковый номер указанной функции в библиотеке равен 4.
В третьей строке из DLL-библиотеки dllsrc.dll импортируется функция с именем TellMe, причем ее порядковый номер не используется.
Для увеличения скорости работы приложения рекомендуется использовать способ, основанный на ссылке по имени DLL-библиотеки и порядковому номеру функции внутри этой библиотеки.
Использование плавающего меню в органе управления EDIT
Как вы знаете, орган управления, созданный на базе предопределенного класса "edit", является простым редактором текста. В приложении SMARTPAD, которое будет описано немного позже, используется интересный прием, позволяющий вызвать на экран плавающее меню простым нажатием правой клавиши мыши внутри окна редактирования. Причем меню окажется как раз около курсора мыши, так что для работы с меню вам не придется передвигать мышь на большое расстояние.
Для редактора текста внутри операционной системы Windows определена функция окна, выполняющая всю работу по редактированию текста, выделению фрагментов текста, копирование выделенного фрагмента в универсальный буфер обмена Clipboard и т. д. Когда вы устанавливаете курсор мыши в окно редактирования и нажимаете правую клавишу мыши, сообщение WM_RBUTTONDOWN попадает в функцию окна редактора текста.
Однако функция родительского окна, создавшая редактор текста, получает только сообщение с кодом WM_COMMAND, да и то только при выполнении определенных операций с текстом. Поэтому сколько бы вы не нажимали правую кнопку мыши в окне редактора текста, родительское окно об этом никогда не узнает.
Как же нам быть? Ведь нам надо не только определить момент, в который пользователь нажал правую кнопку мыши, но и узнать текущие координаты курсора мыши, чтобы создать плавающее меню в нужном месте экрана (недалеко от курсора мыши).
Так как встроенная функция окна, используемая редактором текста, перехватывает сообщение WM_RBUTTONDOWN и "не выпускает" его наружу, нам надо вставить собственный обработчик сообщений перед стандартным для класса окна "edit".
Программный интерфейс Windows позволяет нам это сделать.
Определим в программе две переменные:
WNDPROC lpfnEditOldWndProc; WNDPROC lpfnEditWndProc;
Эти переменные будут использоваться для хранения, соответственно, указателя на старую функцию окна редактора текста и указателя на новую функцию окна редактора текста.
Для получения адреса функции окна редактора текста мы воспользуемся функцией GetWindowLong :
lpfnEditOldWndProc = (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC);
Если в качестве второго параметра этой функции передать константу GWL_WNDPROC , функция вернет адрес функции окна, идентификатор которого задан первым параметром. Возвращенное функцией GetWindowLong значение мы сохраним в переменной lpfnEditOldWndProc, так как наша функция окна, встроенная до стандартной, после выполнения своей задачи должна вызвать стандартную функцию окна (иначе редактор текста не будет работать).
Итак, адрес старой функции окна мы узнали. Теперь надо подготовить новую функцию окна, которая, если пользователь нажмет на правую клавишу мыши, будет выводить на экран плавающее меню. Вот эта функция:
// ====================================================== // Новая функция окна для редактора текста // ====================================================== LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Если в окне редактора текста пользователь нажал // правую клавишу мыши, выводим в позиции курсора мыши // плавающее меню if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt;
// Преобразуем координаты курсора мыши в экранные pt = MAKEPOINT(lParam); ClientToScreen(hwnd, &pt);
// Создаем пустое временное меню hmenuPopup = CreatePopupMenu();
// Заполняем временное меню AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open"); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save"); AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0); AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit");
// Выводим плавающее меню в позиции курсора мыши TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL);
// Удаляем временное меню DestroyMenu(hmenuPopup); } // Вызываем старую функцию окна редактора текста return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam); }
Обратите внимание, что после завершения работы новая функция окна вызывает старую функцию окна. Так как ваше приложение не может вызывать функцию окна непосредственно, мы вызываем старую функцию окна при помощи функции CallWindowProc .
Таким образом, мы сделали то, что нам нужно - новая функция окна обрабатывает сообщение от правой клавиши мыши, выводит плавающее меню и затем вызывает стандартную функцию окна текстового редактора.
Однако для того чтобы вместо стандартной функции окна вызывалась наша, ее необходимо подключить при помощи функции SetWindowLong :
lpfnEditWndProc = (WNDPROC)MakeProcInstance((FARPROC)EditWndProc,hInst); SetWindowLong(hEdit, GWL_WNDPROC, (LONG)lpfnEditWndProc);
Перед вызовом функции мы создаем переходник и сохраняем его адрес в переменной lpfnEditWndProc.
Сразу после возвращения управления из функции SetWindowLong наша новая функция окна включается в работу, пропуская через себя все сообщения, предназначенные для стандартной функции окна редактора текста.
Описанная выше методика обычно используется в тех случаях, когда нужно изменить поведение стандартного органа управления или любого стандартного окна Windows с известным идентификатором (зная который можно "добраться" до функции окна).
Использование стандартной библиотеки транслятора
Для работы с файлами в приложениях Windows вы можете использовать функции из стандартной библиотеки транслятора C или C++, такие как read , fread , write , fwrite , lseek , fstat , tell , close .
Можно также использовать функции потокового ввода/вывода , если преобразовать идентификатор файла в указатель на структуру FILE при помощи функции fdopen . Для того чтобы закрыть такой файл следует воспользоваться функцией fclose .
Перечисленные выше функции были описаны в третьей книге первого тома "Библиотеки системного программиста" в главе "Файловая система DOS".
Изменение блока памяти
Как мы уже говорили, функция GlobalReAlloc , описанная выше, позволяет изменить характеристики глобального блока памяти.
Если вам, например, надо изменить размер заказанного ранее блока памяти, сделав его равным 51200 байт, вы можете для этого использовать следующий фрагмент кода:
hmemGlobal = GlobalReAlloc(hmemGlobal, 51200, GMEM_MODIFY | GMEM_DISCARDABLE | GMEM_MOVEABLE | GMEM_ZEROINIT);
После вызова функции блок памяти будет перемещаемый и удаляемый, причем если раньше его размер был меньше 51200 байт, во все байты дополнительной памяти будут записаны нулевые значения.
Учтите, что при увеличении размера блока может возникнуть ситуация нехватки памяти, поэтому проверяйте значение идентификатора, возвращаемой функцией GlobalReAlloc, на неравенство константе NULL.
Вы можете инициировать удаление блока памяти при помощи функции GlobalReAlloc, если укажите нулевой размер блока и флаг GMEM_MOVEABLE. В файле windows.h имеется определение макрокоманды GlobalDiscard, при помощи которой приложение может принудительно удалить блок из памяти:
#define GlobalDiscard(h) GlobalReAlloc(h, 0L, GMEM_MOVEABLE)
Изменение строк
Для изменения строк (элементов) существующего меню вы можете воспользоваться функцией ModifyMenu :
BOOL WINAPI ModifyMenu(HMENU hmenu, UINT idItem, UINT fuFlags, UINT idNewItem, LPCSTR lpszNewItem);
Параметры этой функции идентичны параметрам функции InsertMenu.
Функция ModifyMenu заменяет указанный элемент меню на новый. При замещении временного меню оно уничтожается и все связанные с ним ресурсы освобождаются.
После того как вы изменили меню, не забудьте вызывать функцию DrawMenuBar, описанную выше.
В программном интерфейсе Windows версии 3.0 была определена функция ChangeMenu , предназначенная для изменения существующего меню. В версии 3.1 эта функция была заменена на следующие пять функций:
Функция | Описание |
AppendMenu | Добавление элемента в меню |
DeleteMenu | Удаление элемента из меню |
InsertMenu | Вставка элемента в меню |
ModifyMenu | Изменение элемента меню |
RemoveMenu | Удаление элемента меню без освобождения ресурсов, занимаемых этим элементом |
Новые приложения не должны пользоваться функцией ChangeMenu.
Изменения в цикле обработки сообщений
Для использования акселераторов цикл обработки сообщений должен выглядеть следующим образом:
while(GetMessage(&msg, 0, 0, 0)) { if(!haccel !TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
В этом фрагменте кода переменная haccel содержит идентификатор загруженной таблицы акселераторов. Если идентификатор не равен NULL, вызывается функция TranslateAccelerator . Эта функция ищет в очереди сообщений сообщения от клавиатуры, соответствующие определенным в ресурсах приложения акселераторам, преобразуя такие сообщения в сообщения WM_COMMAND и WM_SYSCOMMAND (если сообщение соответствует системному меню), передаваемые непосредственно в функцию окна, минуя очередь сообщений приложения .
Содержимое параметра wParam в последних двух сообщениях равно идентификатору, указанному в таблице акселераторов для данной комбинации клавиш.
Старшее слово параметр lParam содержит 1 для сообщений, которые пришли от акселераторов и 0 для сообщений, которые пришли от меню.
Приведем прототип функции TranslateAccelerator:
int WINAPI TranslateAccelerator(HWND hwnd, HACCEL haccel, MSG FAR* lpmsg);
Параметр hwnd определяет идентификатор окна, для которого выполняется преобразование клавиатурных сообщений.
Параметр haccel должен содержать идентификатор загруженной при помощи функции LoadAccelerators таблицы акселераторов.
Последний параметр lpmsg является указателем на структуру типа MSG, в которую должно быть записано обрабатываемое сообщение.
Если функция TranslateAccelerator выполнила преобразование сообщения, она возвращает ненулевое значение. В противном случае возвращается0. Обработанное сообщение не следует передавать функциям TranslateMessage и DispatchMessage.
Экспортируемые функции
Кроме функций LibMain и WEP в DLL-библиотеке могут быть определены и другие функции (как мы уже говорили, существуют DLL-библиотеки, состоящие из одних ресурсов). Это могут быть экспортируемые и неэкспортируемые функции.
Экспортируемые функции доступны для вызова приложениям Windows. Неэкспортируемые являются локальными для DLL-библиотеки, они доступны только для функций библиотеки.
Самый простой способ сделать функцию экспортируемой в системе разработки Borland Turbo C++ for Windows - указать в ее определении ключевое слово _export. Мы использовали этот способ при определении функции окна. Для экспортируемой функции создается специальный пролог, необходимый для правильной загрузки сегментного регистра DS.
Есть и другой способ - можно перечислить все экспортируемые функции в файле определения модуля при помощи оператора EXPORTS :
EXPORTS DrawBitmap @4 ShowAll HideAll GetMyPool @8 FreeMyPool @9
В приведенном выше примере в разделе EXPORTS перечислены имена нескольких экспортируемых функций. Около некоторых имен после символа "@" указаны порядковые номера соответствующих функций в DLL-библиотеке.
Если вы не укажите порядковые номера экспортируемых функций, при компоновке загрузочного файла DLL-библиотеки редактор связи создаст свою собственную нумерацию, которая может изменяться при внесении изменений в исходные тексты функций и последующей повторной компоновке. Плохо это или хорошо?
Для ответа на этот вопрос следует знать, что ссылка на экспортируемую функцию может выполняться двумя различными способами - по имени функции и по ее порядковому номеру. Если функция вызывается по имени, ее порядковый номер не имеет значения. Однако вызов функции по порядковому номеру выполняется быстрее, поэтому использование порядковых номеров предпочтительнее.
Если при помощи файла определения модуля DLL-библиотеки вы задаете фиксированное распределение порядковых номеров экспортируемых функций, при внесении изменений в исходные тексты DLL-библиотеки это распределение не изменится. В этом случае все приложения, ссылающиеся на функции из этой библиотеки по их порядковым номерам, будут работать правильно. Если же вы не определили порядковые номера функций, у приложений могут возникнуть проблемы с правильной адресацией функции из-за возможного изменения этих номеров.
Чтобы сказанное стало более понятным, рассмотрим подробнее механизм, с помощью которого приложения ссылаются на функции из DLL-библиотек.
Классификация типов меню
При создании окна в приложении Windows вы можете указать, что окно должно иметь меню. Обычно меню создается в главном окне приложения. Такое меню мы будем называть меню приложения .
На рис. 1.1 показано главное окно стандартного приложения Paintbrush, имеющее меню.
Рис. 1.1. Меню приложения Paintbrush
На этом рисунке меню приложения Paintbrush располагается ниже заголовка окна. Меню содержит отдельные элементы, или строки ("File", "Edit", "View", и т. д.), расположенные в полосе меню (menu bar ).
Для активизации строки меню вам надо установить на нее курсор и сделать щелчок левой клавишей мыши, либо нажать клавишу <Alt> и затем клавишу, соответствующую подчеркнутой букве. Например, для активизации строки "File" следует использовать клавиши <Alt> и <F>. Если нажать, а затем отпустить клавишу <Alt>, нужную строку в меню приложения можно будет выбрать клавишами перемещения курсора по горизонтали <Left> и <Right>. Для активизации строки в последнем случае после выбора следует нажать клавишу <Enter>. Для отказа от выбора можно воспользоваться клавишей <Esc>.
Строки меню могут быть использованы либо для выбора команд, либо для активизации дополнительных временных меню (pop-up menu ). Как правило, строки меню приложения используются только для активизации временных меню, но не для выполнения команд. Некоторые строки меню могут отображаться серым цветом. Это заблокированные строки, которые не могут быть выбраны.
Временное меню (рис. 1.2) появляется на экране после выбора строки в меню приложения.
Рис. 1.2. Временное меню
Временное меню содержит строки, расположенные в столбец. Для выбора строки из временного меню вы можете воспользоваться мышью или клавишами перемещения курсора по вертикали <Up> и <Down>. В последнем случае для завершения выбора следует нажать клавишу <Enter>. Можно также воспользоваться клавишей <Alt> и клавишей, соответствующей подчеркнутой букве в нужной строке.
Строки временного меню могут быть отмечены галочкой (рис. 1.2). Такие строки обычно используются как переключатели, изменяющие режим работы приложения. Например, если в меню "View" приложения Paintbrush выбрать строку "Cursor Position", слева от строки будет нарисована галочка, а в окне Paintbrush вы увидите текущие координаты курсора мыши. Если выбрать эту же строку еще раз, галочка пропадет. Режим отображения координат курсора будет выключен.
Мы уже говорили, что режимы работы приложения обычно задаются при помощи диалоговых панелей. Однако в простых случаях можно воспользоваться и строками меню.
Если выбор строки меню приводит к выполнению команды (например, команды создания документа, завершения работы приложения, копирования фрагмента документа в универсальный буфер обмена Clipboard и т. д.), строка меню содержит краткое название выполняемой команды, например, "New", "Copy", и т. д. Если же при выборе строки на экране появляется диалоговая панель, к слову справа добавляется многоточие (рис. 1.3). Последнее соглашение не является обязательным, однако вы должны ему следовать для обеспечения стандартного пользовательского интерфейса.
Рис. 1.3. Меню "File" приложения Paintbrush
Вы можете создавать многоуровневые меню . На рис. 1.4. мы привели внешний вид многоуровневого меню из приложения Borland Turbo C++ for Windows версии 3.1. Если из меню "Options" выбрать строку, отмеченную символом "", на экране появится меню второго уровня.
Рис. 1.4. Многоуровневое меню
Можно использовать многократную вложенность меню. Однако мы не советуем вам увлекаться сложными многоуровневыми меню, так как ими трудно пользоваться. В частности, систему настройки параметров Borland Turbo C++ for Windows версии 3.1 трудно назвать удобной в использовании. В Borland C++ for Windows версии 4.01 используется более удобный способ, основанный на применении диалоговых панелей (рис. 1.5).
Рис. 1.5. Диалоговая панель настройки параметров в Borland C++ for Windows версии 4.01
При помощи динамически изменяемого списка "Topics" вам предоставляется возможность выбора нужной группы параметров. В правой части диалоговой панели отображаются переключатели и другие органы управления, соответствующей выбранной группе параметров. Если вы выберете другую группу параметров, вид правой части диалоговой панели изменится соответствующим образом. Такая система настройки параметров намного удобнее многоуровневых меню, использованных в предыдущих версиях Borland C++.
Каждое стандартное приложение Windows имеет системное меню , которое можно вызвать щелчком левой клавиши мыши по окну активизации системного меню, расположенному слева от заголовка окна либо при помощи комбинации клавиши <Alt> и клавиши пробела (рис. 1.6).
Рис. 1.6. Системное меню приложения Paintbrush
Как правило, системное меню любого стандартного приложения содержит строки, показанные на рис. 1.6. С помощью системного меню вы можете минимизировать (строка "Minimize") или максимизировать ("Maximize") главное окно приложения, восстанавливать размер этого окна ("Restore"), перемещать окно ("Move") или изменять его размер ("Size"), закрывать окно ("Close") и переключаться на другие приложения ("Switch To..."). Ваше приложение может изменять системное меню, дополняя его новыми строками или горизонтальными разделительными линиями, удалять строки из существующего меню.
Приложение может создать меню в любом месте экрана. На рис. 1.7 показано меню внутри окна редактирования текста, созданное в среде разработки программ Borland C++ for Windows версии 4.01 щелчком правой клавиши мыши.
Рис. 1.7. Меню внутри окна приложения Borland C++ for Windows версии 4.01
Такое меню называют плавающим (floating menu ), подчеркивая тот факт, что меню может появится в любом месте экрана или окна приложения.
В некоторых случаях плавающие меню удобнее обычных. Вы можете создавать плавающее меню двойным щелчком левой клавиши мыши или щелчком правой клавиши мыши, а также любым другим аналогичным способом.
Если плавающее меню появится вблизи курсора мыши, из него будет легче выбрать нужную строку, чем из обычного меню, так как не надо перемещать курсор в верхнюю часть экрана. Кроме того, создавая плавающее меню щелчком мыши, вы можете изменять внешний вид этого меню в зависимости от объекта, по изображению которого был сделан щелчок.
Это позволит реализовать объектно-ориентированный подход в работе пользователя с приложением - в зависимости от того, для какого объекта было создано плавающее меню, изменяется содержимое меню. Таким образом, для того чтобы выполнить какую-либо операцию над объектом, пользователю будет достаточно щелкнуть по нему мышью. Около объекта появится плавающее меню операций, определенных для данного объекта.
Меню не обязательно должно содержать только текстовые строки. Вы можете создать меню из графических изображений или из комбинации графических изображений и текста. На рис. 1.8 показано меню приложения MENU из примеров приложений, поставляющихся вместе с системой разработки приложений Microsoft SDK для Windows версии 3.1.
Рис. 1.8. Меню приложения MENU, содержащее графические изображения
Для создания меню с графическими изображениями можно использовать методы, аналогичные применяемым при создании органов управления, рисуемых родительским окном, или специальные функции из программного интерфейса Windows.
Кодировка OEM
Так как для работы с файлами операционная система Windows использует файловую систему MS-DOS, для указания пути к файлам и каталогам необходимо использовать кодировку OEM , принятую в MS-DOS. Так как Windows работает с кодировкой ANSI , в некоторых случаях необходимо выполнять соответствующие преобразования, вызывая функции AnsiToOem и OemToAnsi .
Однако при открытии файла с помощью функций OpenFile и _lopen, определенных в программном интерфейсе Windows, вы можете не беспокоиться о кодировке, так как эти функции выполняют сами все необходимые преобразования.
Итак, следующее правило.
Принимайте во внимание то обстоятельство, что Windows и MS-DOS используют разную кодировку. При необходимости выполняйте преобразования из кодировки ANSI в кодировку OEM и обратно.
Количество элементов в меню
Функция GetMenuItemCount возвращает количество элементов в меню верхнего уровня или во временном меню, заданном параметром hmenu:
int WINAPI GetMenuItemCount(HMENU hmenu);
LCustData
Значение, передаваемой функции фильтра через параметр lParam.
Файл menu/menu.cpp
// ---------------------------------------- // Работа с меню // ----------------------------------------
#define STRICT #include <windows.h>
#include <mem.h>
#include "menu.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "MenuClass";
// Заголовок окна char const szWindowTitle[] = "Menu Demo";
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные // параметры
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Подключаем меню wc.lpszMenuName = "APP_MENU";
wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPABOUT: case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_EDITPASTE: case CM_EDITCOPY: case CM_EDITCUT: case CM_EDITUNDO: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: case CM_FILESAVEAS: case CM_FILESAVE: case CM_FILEOPEN: case CM_FILENEW: { // На сообщение от любой строки меню кроме // завершающей работу программы реагируем // выводом сообщения об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; }
default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0);
return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
Функция WinMain регистрирует класс окна и создает на его базе главное окно приложения.
При регистрации класса окна указывается имя шаблона меню, определенного в файле описания ресурсов:
wc.lpszMenuName = "APP_MENU";
В остальном функция WinMain не имеет никаких особенностей.
Функция главного окна WndProc обрабатывает сообщение WM_COMMAND, поступающее от меню. Для всех идентификаторов, кроме CM_FILEEXIT, обработка сводится к выводу сообщения о том, что данная функция не реализована. Если вы из меню 'File" нашего приложения выбираете строку "Exit", обработчик сообщения WM_COMMAND уничтожает главное окно приложения, вызывая функцию DestroyWindow.Это приводит к завершению работы приложения.
Идентификаторы строк меню описаны в файле menu.hpp (листинг 1.2), включаемом при помощи оператора #include в главный файл приложения и файл описания ресурсов.
Файл menu/menu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341
#define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321
#define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331
Файл описания ресурсов (листинг 1.3) содержит определение шаблона меню с именем APP_MENU, описанный нами ранее.
Файл menu/menu.rc
#include "menu.hpp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM "&Open...", CM_FILEOPEN MENUITEM "&Save", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE END
POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, INACTIVE MENUITEM "&Keyboard", CM_HELPKEYBOARD, INACTIVE MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
Проверяя работу приложения, обратите внимание на то, что хотя в строках временного меню "Edit" были указаны комбинации клавиш ускоренного выбора (например, для функции "Undo" указана комбинация клавиш "Ctrl+Z"), вы пока не можете их использовать. Это связано с тем, что мы пока не определили комбинации клавиш ускоренного выбора, а всего лишь записали их обозначение в строках меню. Мы еще вернемся к этому вопросу.
В листинге 1.4 приведен файл определения модуля, использованный при сборке приложения MENU.
Файл menu/menu.def
; ============================= ; Файл определения модуля ; ============================= NAME MENU DESCRIPTION 'Приложение MENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл dmenu/dmenu.cpp
// ---------------------------------------- // Создание меню без использования шаблона // Динамическое изменение меню // ----------------------------------------
#define STRICT #include <windows.h>
#include <mem.h>
#include "dmenu.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "DMenuClass";
// Заголовок окна char const szWindowTitle[] = "Menu Demo";
// Идентификатор меню верхнего уровня HMENU hmenu;
// Идентификаторы временных меню HMENU hmenuFile; // "File" HMENU hmenuEdit; // "Edit" HMENU hmenuHelp; // "Help"
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // Создаем пустое меню верхнего уровня hmenu = CreateMenu();
// Подключаем меню к главному окну приложения SetMenu(hwnd, hmenu);
// Создаем два временных меню - "File" и "Help" hmenuFile = CreatePopupMenu();
hmenuHelp = CreatePopupMenu();
// Добавляем строки к меню "File" AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open");
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILECLOSE, "&Close");
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILESAVE, "&Save");
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILESAVEAS, "Save &as...");
// Добавляем разделительную линию AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPRINT, "&Print");
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPAGE_SETUP, "Page Se&tup");
AppendMenu(hmenuFile, MF_GRAYED | MF_STRING, CM_FILEPRINTER_SETUP, "P&rinter Setup");
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuFile, MF_DISABLED | MF_STRING, CM_FILEDEMO, "&Demo Version");
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEEXIT, "E&xit");
// Отмечаем галочкой строку "Demo Version" CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
// Добавляем строки к меню "Help" AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPINDEX, "&Index\tF1");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPKEYBOARD, "&Keyboard");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPCOMMANDS, "&Commands");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPPROCEDURES, "&Procedures");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPUSING_HELP, "&Using help");
AppendMenu(hmenuHelp, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuHelp, MF_ENABLED | MF_STRING, CM_HELPABOUT, "&About...");
// Добавляем временные меню к меню верхнего уровня AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File");
AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help");
// Записываем в идентификатор меню "Edit" значение // NULL. Если это меню не будет создано, мы не будем // вызывать функцию DestroyMenu для его уничтожения hmenuEdit = NULL;
// Перерисовываем меню DrawMenuBar(hwnd);
return 0; }
case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_EDITPASTE: case CM_EDITCOPY: case CM_EDITCUT: case CM_EDITUNDO: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: case CM_FILESAVEAS: case CM_FILESAVE: { // Выводим сообщение об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK);
return 0; }
// Выбрали строку "About..." в меню "Help" case CM_HELPABOUT: { MessageBox(hwnd, "Приложение DMENU\n(C) Фролов А.В., 1994", szWindowTitle, MB_OK | MB_ICONINFORMATION);
return 0; }
// Выбрали строки "Open" или "New" в меню "File" case CM_FILEOPEN: case CM_FILENEW: { // Создаем временное меню "Edit" hmenuEdit = CreatePopupMenu();
// Добавляем строки в меню "Edit" AppendMenu(hmenuEdit, MF_GRAYED | MF_STRING, CM_EDITUNDO, "&Undo\tCtrl+Z");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCUT, "&Cut\tCtrl+X");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCOPY, "&Copy\tCtrl+C");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITPASTE, "&Paste\tCtrl+V");
// Вставляем меню "Edit" между меню "File" // и в меню "Help" InsertMenu(hmenu, 1, MF_BYPOSITION | MF_ENABLED | MF_POPUP, (UINT)hmenuEdit, "&Edit");
// Разблокируем строки "Save", "Save as..." // и "Close" в меню "File" EnableMenuItem(hmenuFile, CM_FILESAVE, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_ENABLED | MF_BYCOMMAND);
// Блокируем строки "New" и "Open" в меню "File" EnableMenuItem(hmenuFile, CM_FILENEW, MF_GRAYED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_GRAYED | MF_BYCOMMAND);
// Перерисовываем меню DrawMenuBar(hwnd);
return 0; }
// Выбрали строку "Close" из меню "File" case CM_FILECLOSE: { // Уничтожаем временное меню "Edit" DestroyMenu(hmenuEdit);
// Удаляем соответствующую строку из меню // верхнего уровня RemoveMenu(hmenu, 1, MF_BYPOSITION);
// Блокируем строки "Save", "Save as..." // и "Close" в меню "File" EnableMenuItem(hmenuFile, CM_FILESAVE, MF_GRAYED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_GRAYED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_GRAYED | MF_BYCOMMAND);
// Разблокируем строки "New" и "Open" в меню "File" EnableMenuItem(hmenuFile, CM_FILENEW, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_ENABLED | MF_BYCOMMAND);
// Перерисовываем меню DrawMenuBar(hwnd);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; }
default: return 0; } }
case WM_DESTROY: { // Если было создано меню "Edit", // уничтожаем его if(hmenuEdit != NULL) { DestroyMenu(hmenuEdit);
}
// Уничтожаем созданные ранее меню DestroyMenu(hmenuFile);
DestroyMenu(hmenuHelp);
DestroyMenu(hmenu);
PostQuitMessage(0);
return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
В приложении определены четыре глобальные переменные типа HMENU, предназначенные для хранения идентификаторов одного меню верхнего уровня (переменная hmenu) и трех временных меню (переменные hmenuFile, hmenuEdit, hmenuHelp).
Меню верхнего уровня создается в функции главного окна приложения во время обработки сообщения WM_CREATE. Созданное пустое меню подключается к главному окну приложения при помощи функции SetMenu:
hmenu = CreateMenu();
SetMenu(hwnd, hmenu);
Далее создаются два временных меню - "File" и "Help", для чего два раза вызывается функция CreatePopupMenu:
hmenuFile = CreatePopupMenu();
hmenuHelp = CreatePopupMenu();
На данный момент все меню пустые. Прежде всего обработчик сообщения WM_CREATE добавляет к меню "File" несколько строк, вызывая соответствующее число раз функцию AppendMenu:
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open");
....... и т. д. .......
Обратите внимание на способ, которым в меню добавляется разделительная линия:
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
Если в качестве второго параметра функции AppendMenu указано значение MF_SEPARATOR, третий и четвертый параметр этой функции игнорируются.
Для того чтобы отметить галочкой строку "Demo Version", вызывается функция CheckMenuItem:
CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
Аналогичным образом формируется меню "Help".
Далее сформированные временные меню "File" и "Help" добавляются к меню верхнего уровня при помощи функции AppendMenu:
AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File");
AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help");
В заключение вызывается функция DrawMenuBar, которая отображает внесенные изменения на экране:
DrawMenuBar(hwnd);
После формирования меню от него в функцию окна начинают поступать сообщения WM_COMMAND.
Так как меню, которые вы создаете, занимают системные ресурсы, их необходимо уничтожать, если они больше не нужны. При завершении работы приложения мы удалим все созданные меню. Однако меню "Edit" может так и не быть создано, так как вы можете сразу после запуска завершить работу приложения. Для того чтобы определить, нужно ли удалять меню "Edit", мы при создании главного окна приложения записываем в переменную hmenuEdit, предназначенную для хранения идентификатора меню, значение NULL:
hmenuEdit = NULL;
Если меню "Edit" будет создано, в переменную hmenuEdit будет записано значение соответствующего идентификатора. При завершении работы приложения мы проверим состояние этой переменной и, если ее содержимое отлично от значения NULL, уничтожим меню.
На многие из этих сообщений функция окна реагирует выводом сообщения о том, что данная функция не реализована. При выборе строки "About..." в меню "Help" на экран выводится диалоговая панель с сообщением о названии приложения и сведения о разработчике. Это стандартная реакция на выбор строки "About..." в меню "Help" любого приложения Windows.
Когда вы выбираете из меню "File" строки "New" или "Open", в функцию окна приложения приходит сообщение WM_COMMAND со значением парамера wParam, равным, соответственно, CM_FILENEW и CM_FILEOPEN. В ответ на эти сообщения создается новое временное меню "Edit", которое вставляется между временными меню "File" и временным меню "Help":
hmenuEdit = CreatePopupMenu();
AppendMenu(hmenuEdit, MF_GRAYED | MF_STRING, CM_EDITUNDO, "&Undo\tCtrl+Z");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCUT, "&Cut\tCtrl+X");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITCOPY, "&Copy\tCtrl+C");
AppendMenu(hmenuEdit, MF_ENABLED | MF_STRING, CM_EDITPASTE, "&Paste\tCtrl+V");
Для вставки меню вызывается функция InsertMenu:
InsertMenu(hmenu, 1, MF_BYPOSITION | MF_ENABLED | MF_POPUP, (UINT)hmenuEdit, "&Edit");
В качестве второго параметра этой функции передается значение 1. Так как в третьем параметре указан флаг MF_BYPOSITION, функция вставит меню перед временным меню с номером 1, т. е. перед меню "Help".
Затем в меню "File" разблокируются строки "Save", "Save as...", "Close" и блокируются строки "New" и "Open":
EnableMenuItem(hmenuFile, CM_FILESAVE, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILESAVEAS, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILECLOSE, MF_ENABLED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILENEW, MF_GRAYED | MF_BYCOMMAND);
EnableMenuItem(hmenuFile, CM_FILEOPEN, MF_GRAYED | MF_BYCOMMAND);
В заключение вызывается функция DrawMenuBar, отображающая внесенные в меню изменения.
Если вы выберите из меню "File" строку "Close", функция окна получит сообщение WM_COMMAND со значением параметра wParam, равным CM_FILECLOSE. Соответствующий обработчик уничтожает временное меню "Edit" (документ закрыт, редактировать нечего), и удаляет соответствующую строку из меню верхнего уровня:
DestroyMenu(hmenuEdit);
RemoveMenu(hmenu, 1, MF_BYPOSITION);
После этого в меню "File" блокируются строки "Save", "Save as...", "Close" и разблокируются строки "New" и "Open". Для этой цели вызывается функция EnableMenuItem. Для отображения внесенных изменений вызывается функция DrawMenuBar.
При завершении работы приложения мы проверяем содержимое переменной hmenuEdit. Если в момент завершения работы приложения меню "Edit" не существует, в этой переменной находится значение NULL. В этом случае мы не вызываем функцию DestroyWindow. Остальные меню уничтожаются всегда:
case WM_DESTROY: { if(hmenuEdit != NULL) { DestroyMenu(hmenuEdit);
} DestroyMenu(hmenuFile);
DestroyMenu(hmenuHelp);
DestroyMenu(hmenu);
PostQuitMessage(0);
return 0; }
Несмотря на то, что при уничтожении окна все связанные с ним меню также уничтожаются, будет лучше, если ваше приложение удалит все созданные им объекты самостоятельно. Такое поведение отвечает правилам "хорошего тона" для приложений Windows, которые совместно используют многие системные ресурсы.
Идентификаторы строк меню описаны во включаемом файле dmenu.hpp (листинг 1.6).
Файл dmenu/dmenu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341
#define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321
#define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331 #define CM_FILECLOSE 24330 #define CM_FILEDEMO 24329
Файл определения модуля приложения приведен в листинге 1.7.
Файл dmenu/dmenu.rc
; ============================= ; Файл определения модуля ; ============================= NAME DMENU DESCRIPTION 'Приложение DMENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл smartpad/smartpad.cpp
#define STRICT #include <windows.h>
#include <commdlg.h>
#include <mem.h>
#include <string.h>
#include <stdlib.h>
#include "toolbar.hpp" #include "smartpad.hpp"
// ====================================================== // Прототипы функций // ======================================================
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg,WPARAM wParam,LPARAM lParam);
HFILE OpenFile(void);
HFILE OpenSaveFile(void);
int SaveFileAs(HWND hwnd);
int SaveFile(HWND hwnd);
int SelectFile(HWND hwnd);
// ====================================================== // Глобальные переменные // ======================================================
// Имя класса окна char const szClassName[] = "SmartPadAppClass";
// Заголовок окна char const szWindowTitle[] = "Smart Pad";
// Глобальная переменная для хранения идентификатора // текущей копии приложения HINSTANCE hInst;
// Указатель на объект органа управления TOOLBAR Toolbar *Tb;
// Переменные для хранения идентификаторов меню HMENU hmenuAppMenu; HMENU hmenuSystemMenu;
// Идентификатор таблицы акселераторов HACCEL haccel;
// Идентификатор редактора текста HWND hEdit;
// Признак внесения изменений в текст BOOL bNeedSave;
// Путь к редактируемому файлу char szCurrentFileName[128];
// Временный буфер char szTempBuffer[128];
// Признак запрета редактирования BOOL bReadOnly = FALSE;
// Идентификаторы файлов HFILE hfSrcFile, hfDstFile;
// Переменные для хранения адресов функций DLGPROC lpfnDlgProc; WNDPROC lpfnEditOldWndProc; WNDPROC lpfnEditWndProc;
// Идентификатор главного окна HWND hwndMain;
// ====================================================== // Функция WinMain // ====================================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Сохраняем идентификатор текущей копии приложения hInst = hInstance;
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Загружаем основное меню приложения hmenuAppMenu = LoadMenu(hInstance, "APP_MENU");
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна hmenuAppMenu, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные параметры
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Сохраняем идентификатор главного окна в // глобальной переменной hwndMain = hwnd;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Создаем орган управления TOOLBAR Tb = new Toolbar(hInstance, hwnd, TB_FIRST);
// Создаем кнопки в органе управления TOOLBAR Tb->
InsertButton(0, MAKEINTRESOURCE(IDB_NEWUP), MAKEINTRESOURCE(IDB_NEWDOWN), MAKEINTRESOURCE(IDB_NEWUP));
Tb->
InsertButton(1, MAKEINTRESOURCE(IDB_OPENUP), MAKEINTRESOURCE(IDB_OPENDOWN), MAKEINTRESOURCE(IDB_OPENGR));
Tb->
InsertButton(2, MAKEINTRESOURCE(IDB_SAVEUP), MAKEINTRESOURCE(IDB_SAVEDOWN), MAKEINTRESOURCE(IDB_SAVEGR));
Tb->
InsertButton(4, MAKEINTRESOURCE(IDB_CUTUP), MAKEINTRESOURCE(IDB_CUTDOWN), MAKEINTRESOURCE(IDB_CUTGR));
Tb->
InsertButton(5, MAKEINTRESOURCE(IDB_COPYUP), MAKEINTRESOURCE(IDB_COPYDOWN), MAKEINTRESOURCE(IDB_COPYGR));
Tb->
InsertButton(6, MAKEINTRESOURCE(IDB_PASTUP), MAKEINTRESOURCE(IDB_PASTDOWN), MAKEINTRESOURCE(IDB_PASTGR));
Tb->
InsertButton(7, MAKEINTRESOURCE(IDB_UNDOUP), MAKEINTRESOURCE(IDB_UNDODOWN), MAKEINTRESOURCE(IDB_UNDOGR));
Tb->
InsertButton(9, MAKEINTRESOURCE(IDB_EXITUP), MAKEINTRESOURCE(IDB_EXITDOWN), MAKEINTRESOURCE(IDB_EXITGR));
Tb->
InsertButton(10, MAKEINTRESOURCE(IDB_HELPUP), MAKEINTRESOURCE(IDB_HELPDOWN), MAKEINTRESOURCE(IDB_HELPGR));
// Загружаем таблицу акселераторов haccel = LoadAccelerators(hInstance, "APP_ACCELERATORS");
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { if(!haccel !TranslateAccelerator(hwnd, haccel, &msg)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} } return msg.wParam; }
// ====================================================== // Функция InitApp // Выполняет регистрацию класса окна // ====================================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации класса окна
// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ====================================================== // Новая функция окна для редактора текста // ====================================================== LRESULT CALLBACK _export EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Если в окне редактора текста пользователь нажал // правую клавишу мыши, выводим в позиции курсора мыши // плавающее меню if(msg == WM_RBUTTONDOWN) { HMENU hmenuPopup; POINT pt;
// Преобразуем координаты курсора мыши в экранные pt = MAKEPOINT(lParam);
ClientToScreen(hwnd, &pt);
// Создаем пустое временное меню hmenuPopup = CreatePopupMenu();
// Заполняем временное меню AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILENEW, "&New");
AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEOPEN, "&Open");
AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILESAVE, "&Save");
AppendMenu(hmenuPopup, MF_SEPARATOR, 0, 0);
AppendMenu(hmenuPopup, MF_BYCOMMAND | MF_ENABLED, CM_FILEEXIT, "E&xit");
// Выводим плавающее меню в позиции курсора мыши TrackPopupMenu(hmenuPopup, TPM_CENTERALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, hwndMain, NULL);
// Удаляем временное меню DestroyMenu(hmenuPopup);
} // Вызываем старую функцию окна редактора текста return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam);
}
// ====================================================== // Функция главного окна приложения WndProc // ====================================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HFONT hfont;
switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 30, 100, 100, hwnd, (HMENU) ID_EDIT, hInst, NULL);
// Задаем для редактора текста шрифт с // переменной шириной символов hfont = GetStockFont(ANSI_VAR_FONT);
SendMessage(hEdit, WM_SETFONT, (WPARAM)hfont, (LPARAM)MAKELONG((WORD)TRUE, 0));
// Создаем переходник для новой функции // окна редактора текста lpfnEditWndProc = (WNDPROC)MakeProcInstance((FARPROC)EditWndProc,hInst);
// Определяем адрес старой функции // окна редактора текста lpfnEditOldWndProc = (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC);
// Подключаем к редактору текста новую функцию окна SetWindowLong(hEdit, GWL_WNDPROC, (LONG)lpfnEditWndProc);
// Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);
// Сбрасываем флаг обновления текста и флаг // запрета редактирования bNeedSave = FALSE; bReadOnly = FALSE;
// Так как редактируемый файл не открывался и не // сохранялся, в переменную пути к нему записываем // пустую строку lstrcpy(szCurrentFileName, "");
// Устанавливаем заголовок окна приложения SetWindowText(hwnd, "SmartPad - [UNTITLED]");
// Определяем идентификатор системного меню hmenuSystemMenu = GetSystemMenu(hwnd, FALSE);
// Добавляем в системное меню разделительную линию // и строку "About" AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0);
AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED, CM_SYSABOUT, "&About...");
// Блокируем в системном меню строку "Close" EnableMenuItem(hmenuSystemMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
return 0; }
case WM_SIZE: { // Устанавливаем новую ширину дочернего окна // органа управления TOOLBAR Tb->
SetWidth(LOWORD(lParam));
// Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 26, LOWORD(lParam), HIWORD(lParam) - 26, TRUE);
return 0; }
// Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit);
return 0; }
// Это сообщение приходит от системного меню case WM_SYSCOMMAND: { // Необходимо замаскировать четыре младших бита // параметра wParam if((wParam & 0xfff0) == CM_SYSABOUT) { // Переходник для функции диалоговой панели lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst);
// Создаем модальную диалоговую панель DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc);
return 0; }
// Блокируем строку "Close". Эта строка не является // обязательной, так как мы уже заблокировали эту // строку функцией EnableMenuItem else if((wParam & 0xfff0) == SC_CLOSE) return 0; break; }
// Сообщение от меню и органа управления TOOLBAR case WM_COMMAND: { switch(wParam) { // Обработка извещений текстового редактора case ID_EDIT: { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK);
}
// Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bNeedSave = TRUE; } return 0; }
// Эти строки меню пока заблокированы, так как // соответствующие функции не реализованы case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_FILEPRINTER_SETUP: case CM_FILEPAGE_SETUP: case CM_FILEPRINT: { MessageBox(hwnd, "В текущей версии " "редактора SmartPad данная функция" " не реализована", NULL, MB_OK);
return 0; }
// На запрос подсказки выводим диалоговую панель // с информацией о программе case TB_HELP: case CM_HELPABOUT: { // Переходник для функции диалоговой панели lpfnDlgProc = (DLGPROC)MakeProcInstance( (FARPROC)DlgProc, hInst);
// Создаем модальную диалоговую панель DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc);
// Ликвидируем переходник FreeProcInstance((FARPROC) lpfnDlgProc);
return 0; }
// Переключение режима запрета редактирования case CM_EDITSETREADONLY: { // Если режим запрета редактирования выключен, // включаем его if(!bReadOnly) { // Запрещаем редактирование SendMessage(hEdit, EM_SETREADONLY, TRUE, 0L);
// Отмечаем соответствующую строку в меню CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY, MF_BYCOMMAND | MF_CHECKED);
// Устанавливаем флаг запрета редактирования bReadOnly = TRUE; } // Если режим запрета редактирования включен, // выключаем его else { SendMessage(hEdit, EM_SETREADONLY, FALSE, 0L);
CheckMenuItem(hmenuAppMenu, CM_EDITSETREADONLY, MF_BYCOMMAND | MF_UNCHECKED);
bReadOnly = FALSE; }
// Устанавливаем фокус ввода на редактор текста SetFocus(hEdit);
return 0; }
case CM_EDITPASTE: case TB_PAST: { SendMessage(hEdit, WM_PASTE, 0, 0L);
SetFocus(hEdit);
return 0; }
case CM_EDITCOPY: case TB_COPY: { SendMessage(hEdit, WM_COPY, 0, 0L);
SetFocus(hEdit);
return 0; }
case CM_EDITCUT: case TB_CUT: { SendMessage(hEdit, WM_CUT, 0, 0L);
SetFocus(hEdit);
return 0; }
case CM_EDITCLEAR: { SendMessage(hEdit, WM_CLEAR, 0, 0L);
SetFocus(hEdit);
return 0; }
case CM_EDITSELALL: { SendMessage(hEdit, EM_SETSEL, 0, MAKELPARAM(0, -1));
SetFocus(hEdit);
return 0; }
case CM_EDITUNDO: case TB_UNDO: { SendMessage(hEdit, EM_UNDO, 0, 0L);
SetFocus(hEdit);
return 0; }
// Завершаем работу приложения case TB_EXIT: case CM_FILEEXIT: { // Проверяем флаг обновления if(bNeedSave) { // Если в тексте были изменения, // спрашиваем у пользователя, надо ли // сохранять текст в файле if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { // Если файл ни разу не сохранялся, // спрашиваем путь и имя нового файла if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd);
// Изменяем заголовок главного окна // приложения в соответствии // с именем и путем к файлу wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
}
// Если файл уже сохранялся, записываем его // на прежнее место else SaveFile(hwnd);
} }
// Завершаем работу приложения DestroyWindow(hwnd);
return 0; }
case CM_FILENEW: case TB_NEW: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd);
wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
} else SaveFile(hwnd);
} }
// Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0");
bNeedSave = FALSE;
lstrcpy(szCurrentFileName, "");
SetWindowText(hwnd, "SmartPad - [UNTITLED]");
SetFocus(hEdit);
return 0; }
case CM_FILEOPEN: case TB_OPEN: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) SaveFile(hwnd);
}
if(!SelectFile(hwnd)) { lstrcpy(szCurrentFileName, "");
SetWindowText(hwnd, "SmartPad - [UNTITLED]");
} else { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
}
return 0; }
case CM_FILESAVEAS: { if(SaveFileAs(hwnd)) { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
} else { lstrcpy(szCurrentFileName, "");
SetWindowText(hwnd, "SmartPad - [UNTITLED]");
} return 0; }
case CM_FILESAVE: case TB_SAVE: { if(szCurrentFileName[0] == '\0') { if(SaveFileAs(hwnd)) { wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
} else { lstrcpy(szCurrentFileName, "");
SetWindowText(hwnd, "SmartPad - [UNTITLED]");
} } else SaveFile(hwnd);
return 0; }
default: break; } break; }
// Это сообщение приходит при завершении работы // операционной системы Windows. Если мы не сохранили // редактируемый текст, спрашиваем у пользователя, // надо ли это делать case WM_QUERYENDSESSION: { // Проверяем флаг обновления if(bNeedSave) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) { if(szCurrentFileName[0] == '\0') { SaveFileAs(hwnd);
wsprintf(szTempBuffer, "SmartPad - [%s]", (LPSTR)szCurrentFileName);
SetWindowText(hwnd, szTempBuffer);
} else SaveFile(hwnd);
} }
// Разрешаем операционной системе Windows // завершить свою работу return 1L; }
case WM_DESTROY: { PostQuitMessage(0);
return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam);
}
// ====================================================== // Функция OpenFile // Сохранение файла // ====================================================== HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn;
// Буфер для записи пути к выбранному файлу char szFile[256];
// Буфер для записи имени выбранного файла char szFileTitle[256];
// Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";
// Идентификатор открываемого файла HFILE hf;
// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';
// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));
// Инициализируем нужные нам поля
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
// Выбираем входной файл if (GetOpenFileName(&ofn)) {
// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);
// Сохраняем путь к открытому файлу lstrcpy(szCurrentFileName, ofn.lpstrFile);
// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }
// ====================================================== // Функция OpenSaveFile // Выбор файла для редактирования // ====================================================== HFILE OpenSaveFile(void) { OPENFILENAME ofn;
char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0";
HFILE hf;
szFile[0] = '\0';
memset(&ofn, 0, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle);
ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY;
// Выбираем выходной файл if (GetSaveFileName(&ofn)) {
// При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0);
// Сохраняем путь к файлу lstrcpy(szCurrentFileName, ofn.lpstrFile);
return hf; } else return 0; }
// ====================================================== // Функция SaveFileAs // Сохранение текста в новом файле // ====================================================== int SaveFileAs(HWND hwnd) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;
// Открываем выходной файл hfDstFile = OpenSaveFile();
if(!hfDstFile) return 0;
// Определяем размер текста wSize = GetWindowTextLength(hEdit);
// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);
// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
// Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile);
MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK);
return 0; }
// Закрываем файл _lclose(hfDstFile);
// Расфиксируем блок памяти LocalUnlock(hTxtBuf);
// Так как файл был только что сохранен, // сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit);
return 1; }
// ====================================================== // Функция SaveFile // Сохранение текста в старом файле // ====================================================== int SaveFile(HWND hwnd) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;
// Открываем выходной файл hfDstFile = _lopen(szCurrentFileName, OF_WRITE);
if(!hfDstFile) return 0;
// Определяем размер текста wSize = GetWindowTextLength(hEdit);
// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);
// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
// Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile);
MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK);
return 0; }
// Закрываем файл _lclose(hfDstFile);
// Расфиксируем блок памяти LocalUnlock(hTxtBuf);
// Так как файл был только что сохранен, // сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit);
return 1; }
// ====================================================== // Функция SelectFile // Загрузка текста из файла для редактирования // ====================================================== int SelectFile(HWND hwnd) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos;
// Открываем входной файл. hfSrcFile = OpenFile();
if(!hfSrcFile) return 0;
// Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1);
dwFileSize = _llseek(hfSrcFile, 0L, 2);
_llseek(hfSrcFile, dwCurrentPos, 0);
// Размер файла не должен превосходить 32000 байт if(dwFileSize >
= 32000) { _lclose(hfSrcFile);
MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK);
return 0; }
// Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000);
if(lpTextBuffer == NULL) return 0;
// Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize);
// Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0';
// Закрываем файл _lclose(hfSrcFile);
// Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer);
// Освобождаем буфер free((void *)lpTextBuffer);
// Сбрасываем флаг обновления bNeedSave = FALSE; SetFocus(hEdit);
return 1; }
// ====================================================== // Функция DlgProc // ====================================================== #pragma argsused
BOOL CALLBACK _export DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Инициализация диалоговой панели case WM_INITDIALOG: { return TRUE; }
case WM_COMMAND: { switch(wParam) { // Сообщение от кнопки "OK" case IDOK:
// Отмена диалоговой панели. // Это сообщение приходит, когда пользователь // нажимает на клавишу <Esc>
case IDCANCEL: { // Устанавливаем флаг завершения диалога EndDialog(hdlg, 0);
return TRUE; } } } } return FALSE; }
В области глобальных переменных среди прочих определений располагается определение указателя на класс Toolbar:
Toolbar *Tb;
Через этот указатель (разумеется, после создания объекта и инициализации указателя) будет выполняться одна из операций, определенных для Toolbar, - создание кнопки в заданной позиции.
Отметим также переменную bNeedSave типа BOOL, которая используется как признак необходимости сохранения редактируемого текста в файле.
В массиве szCurrentFileName[128] хранится путь к редактируемому файлу. Эта информация используется для формирования заголовка главного окна приложения и для сохранения файла при помощи строки "Save" из меню "File" или кнопки в Toolbar с изображением дискеты.
С помощью строки "Read Only" меню "Options" вы можете запретить редактирование текста.
При этом в глобальную переменную bReadOnly типа BOOL записывается значение TRUE.
Три глобальные переменные используются для хранения, соответственно, адреса функции диалога, появляющегося при выборе строки "About..." (рис. 1.20), адреса стандартной функции диалога редактора текста и новой функции диалога, необходимой для вывода плавающего меню.
Рис. 1.20. Диалоговая панель "About" приложения SMARTPAD
Функция WinMain инициализирует приложение обычным образом и загружает основное меню из ресурсов приложения, вызывая функцию LoadMenu. Далее создается главное окно приложения, идентификатор которого сохраняется в глобальной переменной hwndMain.
После отображения главного окна создается орган управления Toolbar :
Tb = new Toolbar(hInstance, hwnd, TB_FIRST);
Конструктору объекта Toolbar передается идентификатор копии приложения hInstance, идентификатор окна, в котором необходимо расположить Toolbar и функция которого будет получать сообщение WM_COMMAND, и константу TB_FIRST, определяющую значение параметра wParam сообщения WM_COMMAND для самой левой кнопки в органе управления Toolbar. Для второй кнопки слева значение этого параметра будет равно TB_FIRST + 1, для третьей TB_FIRST + 2, и т. д.
Далее функция WinMain создает все необходимые кнопки, вызывая метод InsertButton, определенный в классе Toolbar:
Tb->
InsertButton(0, MAKEINTRESOURCE(IDB_NEWUP), MAKEINTRESOURCE(IDB_NEWDOWN), MAKEINTRESOURCE(IDB_NEWUP));
Tb->
InsertButton(1, MAKEINTRESOURCE(IDB_OPENUP), MAKEINTRESOURCE(IDB_OPENDOWN), MAKEINTRESOURCE(IDB_OPENGR));
Tb->
InsertButton(2, MAKEINTRESOURCE(IDB_SAVEUP), MAKEINTRESOURCE(IDB_SAVEDOWN), MAKEINTRESOURCE(IDB_SAVEGR));
Tb->
InsertButton(4, MAKEINTRESOURCE(IDB_CUTUP), MAKEINTRESOURCE(IDB_CUTDOWN), MAKEINTRESOURCE(IDB_CUTGR));
Tb->
InsertButton(5, MAKEINTRESOURCE(IDB_COPYUP), MAKEINTRESOURCE(IDB_COPYDOWN), MAKEINTRESOURCE(IDB_COPYGR));
Tb->
InsertButton(6, MAKEINTRESOURCE(IDB_PASTUP), MAKEINTRESOURCE(IDB_PASTDOWN), MAKEINTRESOURCE(IDB_PASTGR));
Tb->
InsertButton(7, MAKEINTRESOURCE(IDB_UNDOUP), MAKEINTRESOURCE(IDB_UNDODOWN), MAKEINTRESOURCE(IDB_UNDOGR));
Tb->
InsertButton(9, MAKEINTRESOURCE(IDB_EXITUP), MAKEINTRESOURCE(IDB_EXITDOWN), MAKEINTRESOURCE(IDB_EXITGR));
Tb->
InsertButton(10, MAKEINTRESOURCE(IDB_HELPUP), MAKEINTRESOURCE(IDB_HELPDOWN), MAKEINTRESOURCE(IDB_HELPGR));
Первый параметр функции InsertButton определяет расположение кнопки в области, выделенной для ToolBar. Обратите внимание, что номера кнопок увеличиваются не монотонно. После кнопки с номером 2 следует кнопка с номером 4, а после кнопки с номером 7 следует кнопка с номером 9. Пропущенным номерам соответствуют пустые позиции в окне Toolbar.
Учтите, что параметр wParam сообщения WM_COMMAND, получаемого от Toolbar, зависит от номера позиции и от значения константы TB_FIRST, переданной конструктору при создании класса. Так как мы не создали кнопки с номерами 3 и 8, функция главного окна приложения никогда не получит сообщение WM_COMMAND с параметром wParam, равным TB_FIRST + 3 и TB_FIRST + 8.
Второй, третий и четвертый параметры функции InsertButton должны содержать идентификаторы изображений bitmap для кнопки в нормальном, нажатом и неактивном состоянии, соответственно.
После создания кнопок функция окна приложения будет получать от Toolbar сообщение WM_COMMAND.
Таким образом, использование класса Toolbar предельно просто. Вы должны создать объект класса Toolbar, указав идентификатор копии приложения, идентификатор главного окна приложения (или другого окна, которое будет содержать Toolbar). Затем вам надо вставить кнопки, вызвав несколько раз функцию InsertButton, которая является методом класса Toolbar. После этого орган управления Toolbar начинает работать, посылая в функцию окна сообщение WM_COMMAND.
Для установки ширины дочернего окна Toolbar определена функция SetWidth. Вы должны вызывать эту функцию в функции главного окна приложения при обработке сообщения WM_SIZE:
case WM_SIZE: { Tb->
SetWidth(LOWORD(lParam));
..... (другие строки) ...... return 0; }
Класс Toolbar определен в файле toolbar.hpp, поэтому в исходный текст программы необходимо включить следующую строку:
#include "toolbar.hpp"
После создания Toolbar функция WinMain загружает с помощью функции LoadAccelerators таблицу акселераторов, используемую для ускоренного доступа к строкам меню, после чего запускается цикл обработки сообщений.
Для работы с акселераторами в цикле обработки сообщений вызывается функция TranslateAccelerators. Для получения символьных сообщений мы должны также вызывать в этом цикле функцию TranslateMessage.
Новая функция окна для редактора текста EditWndProc перехватывает сообщение WM_RBUTTONDOWN, возникающее в тот момент, когда пользователь нажимает правую кнопку мыши, и выводит плавающее меню.
Координаты курсора мыши передаются через параметр lParam. Эти координаты вычислены относительно верхнего левого угла внутренней области окна (client region). Так как функция TrackPopupMenu, создающая плавающее меню, использует экранные координаты, мы выполняем соответствующее преобразование с помощью функции ClientToScreen.
Если обработка сообщения от мыши завершена или пришло другое сообщение, предназначенное для функции окна редактора текста, новая функция окна EditWndProc передает сообщение без изменений стандартной функции окна через переходник lpfnEditOldWndProc:
return CallWindowProc(lpfnEditOldWndProc, hwnd, msg, wParam, lParam);
Теперь рассмотрим работу функции главного окна приложения, которая называется WndProc.
При создании главного окна приложения управление получает обработчик сообщения WM_CREATE. Он создает стандартный редактор текста на базе предопределенного класса окна "edit".
Для того чтобы придать редактируемому тексту более привлекательный вид, изменяем шрифт, используемый редактором текста на шрифт ANSI с переменной шириной букв:
hfont = GetStockFont (ANSI_VAR_FONT);
SendMessage(hEdit, WM_SETFONT , (WPARAM)hfont, (LPARAM)MAKELONG((WORD)TRUE, 0));
Подробное описание этой операции и сообщения WM_SETFONT мы отложим до главы, посвященной шрифтам.
Далее обработчик сообщения WM_CREATE создает переходник для новой функции окна редактора текста, определяет адрес старой (т. е. стандартной) функции окна редактора текста и подключает новую функцию окна. Этот процесс был описан раньше.
После этого устанавливается максимальная длина редактируемого текста, инициализируются флаги, устанавливается новый заголовок главного окна приложения.
Хотя в этом нет никакой необходимости, приложение SMARTPAD изменяет системное меню. Мы сделали это исключительно для иллюстрации методов работы с системным меню.
Сначала с помощью функции GetSystemMenu мы определяем идентификатор системного меню. Затем в системное меню добавляется горизонтальная разделительная линия и строка "About...", при выборе которой на экране появляется диалоговая панель 'About". Для добавления используется функция AppendMenu.
Мы также блокируем строку "Close" в системном меню, для чего вызываем функцию EnableMenuItem. Напомним, что все стандартные строки системного меню имеют идентификаторы, для которых в файле windows.h определены символические константы. В честности, строка "Close" имеет идентификатор SC_CLOSE.
Обработчик сообщения WM_SIZE устанавливает новую ширину дочернего окна Toolbar и новый размер редактора текста:
{ Tb->
SetWidth(LOWORD(lParam));
MoveWindow(hEdit, 0, 26, LOWORD(lParam), HIWORD(lParam) - 26, TRUE);
return 0; }
Обработчик сообщения WM_FOCUS передает фокус окну редактора текста, вызывая функцию SetFocus.
Для обработки сообщений от системного меню в функции главного окна приложения предусмотрен обработчик сообщения WM_SYSCOMMAND. Мы уже рассказывали вам об особенности этого сообщения - перед сравнением младшие четыре бита параметра wParam необходимо замаскировать.
Обработчик сообщения WM_SYSCOMMAND реагирует на добавленную нами в системное меню строку "About..." с идентификатором CM_SYSABOUT. Дополнительно мы перехватываем сообщение от строки "Close" и "изымаем" его, блокируя работу соответствующей строки.
Обработчик сообщения WM_COMMAND получился достаточно сложным, так как наше приложение имеет большое меню и Toolbar. Все основные выполняемые функции объясняются в комментариях, поэтому мы для экономии места остановимся только на некоторых моментах.
Обработка извещений от редактора текста выполняется аналогично тому, как это сделано в приложении TEDIT, описанном в предыдущем томе "Библиотеки системного программиста".
При выборе строки "Read Only" из меню "Options" инвертируется содержимое флага запрета редактирования bReadOnly. Если этот флаг находился в состоянии FALSE, редактору текста посылается сообщение EM_SETREADONLY с параметром WParam, равном TRUE, после чего содержимое флага меняется на TRUE. После этого строка "Read Only" отмечается галочкой при помощи функции CheckMenuItem.
Если выбрать эту строку еще раз, запрет редактирования отменяется, флаг запрета редактирования bReadOnly снова инвертируется, вслед за чем удаляется отметка в строке меню.
Практически после выполнения всех операций, отнимающих фокус ввода у текстового редактора, мы возвращаем фокус редактору, вызывая функцию SetFocus.
Обратите внимание на обработчик сообщения WM_QUERYENDSESSION. Это сообщение передается приложению перед завершением работы операционной системы Windows. Приложение может запретить завершение работы Windows, вернув нулевое значение, или разрешить, вернув значение 1. Наше приложение в ответ на это сообщение проверяет состояние флага bNeedSave. Если текст не был сохранен, на экран выводится соответствующий запрос. Если в ответ на этот запрос вы нажмете клавишу "Yes", текст будет сохранен в файле. После этого обработчик сообщения WM_QUERYENDSESSION возвращает 1, разрешая операционной системе завершить свою работу.
Файл smartpad.hpp содержит определения всех используемых в файле smartpad.cpp символических констант (листинг 1.9).
Файл smartpad/smartpad.hpp
// Идентификаторы пиктограмм #define IDB_NEWUP 300 #define IDB_NEWDOWN 301 #define IDB_NEWGR 302
#define IDB_OPENUP 303 #define IDB_OPENDOWN 304 #define IDB_OPENGR 305
#define IDB_SAVEUP 306 #define IDB_SAVEDOWN 307 #define IDB_SAVEGR 308
#define IDB_EXITUP 309 #define IDB_EXITDOWN 310 #define IDB_EXITGR 311
#define IDB_COPYUP 312 #define IDB_COPYDOWN 313 #define IDB_COPYGR 314
#define IDB_PASTUP 315 #define IDB_PASTDOWN 316 #define IDB_PASTGR 317
#define IDB_CUTUP 318 #define IDB_CUTDOWN 319 #define IDB_CUTGR 320
#define IDB_UNDOUP 321 #define IDB_UNDODOWN 322 #define IDB_UNDOGR 323
#define IDB_HELPUP 324 #define IDB_HELPDOWN 325 #define IDB_HELPGR 326
#define CM_FONT 24347 #define CM_SYSABOUT 0x8880
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341
#define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321 #define CM_EDITCLEAR 24320 #define CM_EDITSELALL 24319 #define CM_EDITSETREADONLY 24318
#define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331
/* Идентификатор редактора текста */ #define ID_EDIT 200
/* Идентификаторы кнопок TOOLBAR */ #define TB_FIRST 100
#define TB_NEW TB_FIRST + 0 #define TB_OPEN TB_FIRST + 1 #define TB_SAVE TB_FIRST + 2
#define TB_CUT TB_FIRST + 4 #define TB_COPY TB_FIRST + 5 #define TB_PAST TB_FIRST + 6 #define TB_UNDO TB_FIRST + 7
#define TB_EXIT TB_FIRST + 9 #define TB_HELP TB_FIRST + 10
Обратите внимание на описание идентификатора строки "About..." системного меню:
#define CM_SYSABOUT 0x8880
Значение этого идентификатора должно отличаться от значений других идентификаторов строк меню, причем младшие четыре бита не должны участвовать в сравнении.
В файле smartpad.rc (листинг 1.10) описаны ресурсы приложения.
Файл smartpad/smartpad.rc
#include "smartpad.hpp"
IDB_NEWUP BITMAP "newup.bmp" IDB_NEWDOWN BITMAP "newdown.bmp" IDB_NEWGR BITMAP "newgr.bmp"
IDB_OPENUP BITMAP "openup.bmp" IDB_OPENDOWN BITMAP "opendown.bmp" IDB_OPENGR BITMAP "opengr.bmp"
IDB_SAVEUP BITMAP "saveup.bmp" IDB_SAVEDOWN BITMAP "savedown.bmp" IDB_SAVEGR BITMAP "savegr.bmp"
IDB_EXITUP BITMAP "exitup.bmp" IDB_EXITDOWN BITMAP "exitdown.bmp" IDB_EXITGR BITMAP "exitgr.bmp"
IDB_COPYUP BITMAP "copyup.bmp" IDB_COPYDOWN BITMAP "copydown.bmp" IDB_COPYGR BITMAP "copygr.bmp"
IDB_PASTUP BITMAP "pastup.bmp" IDB_PASTDOWN BITMAP "pastdown.bmp" IDB_PASTGR BITMAP "pastgr.bmp"
IDB_CUTUP BITMAP "cutup.bmp" IDB_CUTDOWN BITMAP "cutdown.bmp" IDB_CUTGR BITMAP "cutgr.bmp"
IDB_UNDOUP BITMAP "undoup.bmp" IDB_UNDODOWN BITMAP "undodown.bmp" IDB_UNDOGR BITMAP "undogr.bmp"
IDB_HELPUP BITMAP "helpup.bmp" IDB_HELPDOWN BITMAP "helpdown.bmp" IDB_HELPGR BITMAP "helpgr.bmp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New\tCtrl+N", CM_FILENEW MENUITEM "&Open...\tCtrl+O", CM_FILEOPEN MENUITEM "&Save\tCtrl+S", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT, GRAYED MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP, GRAYED MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP,GRAYED MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM SEPARATOR MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE MENUITEM "C&lear\tCtrl+Del", CM_EDITCLEAR MENUITEM SEPARATOR MENUITEM "Select &All", CM_EDITSELALL END
POPUP "&Options" BEGIN MENUITEM "&Read-only", CM_EDITSETREADONLY MENUITEM SEPARATOR MENUITEM "&Set Font...", CM_FONT, GRAYED END
POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, GRAYED MENUITEM "&Keyboard", CM_HELPKEYBOARD, GRAYED MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
APPICON ICON "appicon.ico"
ABOUT DIALOG 25, 34, 118, 67 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" BEGIN CTEXT "Редактор текста\nSmart Pad\nVersion 1.0\n" "(C) Frolov A.V., 1994", -1, 34, 6, 79, 36, WS_CHILD | WS_VISIBLE | WS_GROUP ICON "APPICON", -1, 14, 16, 16, 16, WS_CHILD | WS_VISIBLE DEFPUSHBUTTON "OK", IDOK, 42, 47, 33, 14, WS_CHILD | WS_VISIBLE | WS_TABSTOP END
APP_ACCELERATORS ACCELERATORS BEGIN "N", CM_FILENEW, VIRTKEY, CONTROL "S", CM_FILESAVE, VIRTKEY, CONTROL "O", CM_FILEOPEN, VIRTKEY, CONTROL "Z", CM_EDITUNDO, VIRTKEY, CONTROL "X", CM_EDITCUT, VIRTKEY, CONTROL "C", CM_EDITCOPY, VIRTKEY, CONTROL "V", CM_EDITPASTE, VIRTKEY, CONTROL VK_DELETE, CM_EDITCLEAR, VIRTKEY, CONTROL VK_F1, CM_HELPINDEX, VIRTKEY END
В файле описания ресурсов определена таблица акселераторов APP_ACCELERATORS. Эта таблица устанавливает соответствие между виртуальными кодами клавиш, используемых для ускоренного выбора функций из меню и соответствующие идентификаторы.
В листинге 1.11 изображены файлы *.bmp, на которые есть ссылки в файле описания ресурсов.
Файлы smartpad/*.bmp
newup.bmp | openup.bmp | saveup.bmp | cutup.bmp |
newdown.bmp | opendown.bmp | savedown.bmp | cutdown.bmp |
newgr.bmp | opengr.bmp | savegr.bmp | cutgr.bmp |
copyup.bmp | pastup.bmp | undoup.bmp | exitup.bmp | helpup.bmp |
copydown.bmp | pastdown.bmp | undodown.bmp | exitdown.bmp | helpdown.bmp |
copygr.bmp | pastgr.bmp | undogr.bmp | exitgr.bmp | helpgr.bmp |
Определение класса Toolbar, предназначенного для создания органа управления Toolbar, находится в файле toolbar.hpp (листинг 1.12).
Файл smartpad/toolbar.hpp
#include <windows.h>
#include <windowsx.h>
#include <mem.h>
// Прототип функции окна TOOLBAR LRESULT CALLBACK _export ToolbarWndProc(HWND, UINT, WPARAM, LPARAM);
// ====================================================== // Определение класса TbMain // ====================================================== class TbMain { public: // Идентификатор приложения static HINSTANCE hInst;
// Идентификаторы кнопок в различном состоянии static HBITMAP hbmpUp[20]; // исходное состояние static HBITMAP hbmpDown[20]; // нажатые static HBITMAP hbmpGrayed[20]; // заблокированные
// Идентификатор родительского окна, создавшего TOOLBAR static HWND hwndParent;
// Идентификатор первой кнопки в TOOLBAR static int nFirstId; };
// ====================================================== // Определение класса Toolbar // ====================================================== class Toolbar { RECT rcParent; // размеры родительского окна RECT rcToolbar; // размеры TOOLBAR ATOM aWndClass; // атом для кода возврата char szClassName[20]; // имя класса HWND hwndToolbar; // идентификатор окна TOOLBAR HWND hButton[20]; // идентификаторы кнопок int errno; // код ошибки
public:
// ====================================================== // Вычисление размеров окна TOOLBAR // ====================================================== void GetRect(void) { rcToolbar.left = GetRectLeft();
rcToolbar.top = GetRectTop();
rcToolbar.right = GetRectRihgt();
rcToolbar.bottom = GetRectBottom();
}
// ====================================================== // Регистрация класса для окна TOOLBAR // ====================================================== virtual BOOL RegisterWndClass(void) { WNDCLASS wc; // структура для регистрации класса окна
memset(&wc, 0, sizeof(wc));
wc.style = 0; wc.lpfnWndProc = (WNDPROC) ToolbarWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = TbMain::hInst; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszMenuName = (LPSTR)NULL;
// Определяем кисть для закрашивания окна wc.hbrBackground = GetBrush();
// Имя класса lstrcpy(szClassName, "FrolovAVToolBar");
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ====================================================== // Создание дочернего окна, которое будет использоваться // для размещения кнопок TOOLBAR // ====================================================== void CreateTbWindow(void) { hwndToolbar = CreateWindow( szClassName, // имя класса окна NULL, // заголовок окна WS_CHILDWINDOW | WS_VISIBLE, // стиль окна rcToolbar.left, // указываем расположение rcToolbar.top, // и размеры окна rcToolbar.right, rcToolbar.bottom, TbMain::hwndParent, // идентификатор родительского окна 0, // идентификатор меню TbMain::hInst, // идентификатор приложения NULL);
// указатель на дополнительные параметры }
// ====================================================== // Устанавливаем ширину окна TOOLBAR // ====================================================== void SetWidth(int nWidth) { RECT rcOld;
GetClientRect(hwndToolbar, &rcOld);
MoveWindow(hwndToolbar, rcOld.left, rcOld.top, nWidth, rcOld.bottom, TRUE);
}
// ====================================================== // Вставляем кнопку в TOOLBAR // ====================================================== BOOL InsertButton(UINT nPosition, LPCSTR lpszBmpUp, LPCSTR lpszBmpDown, LPCSTR lpszBmpGrayed) { // Загружаем указанные в параметрах функции изображения TbMain::hbmpUp[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpUp);
TbMain::hbmpDown[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpDown);
TbMain::hbmpGrayed[nPosition] = LoadBitmap(TbMain::hInst, lpszBmpGrayed);
// Создаем орган управления - кнопку, которую // рисует родительское окно. В нашем случае это будет // окно TOOLBAR hButton[nPosition] = CreateWindow("button", NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, GetBtnX(nPosition), // определяем расположение кнопки GetBtnY(nPosition), // исходя из ее номера GetBmpWidth(), // ширина кнопки GetBmpHeigt(), // высота кнопки hwndToolbar, // родительское окно для кнопки
// Идентификатор кнопки (HMENU)(nPosition + TbMain::nFirstId),
TbMain::hInst, NULL);
return TRUE; }
// ====================================================== // Конструктор для класса Toolbar // ====================================================== Toolbar( HINSTANCE hInst, HWND hwndParent, int nFirstid);
// ====================================================== // Деструктор для класса Toolbar // ====================================================== ~Toolbar();
// Проверка признака ошибки int Error(void) { return errno; }
// Определение координат окна TOOLBAR virtual int GetRectLeft(void) { return 0; } virtual int GetRectTop(void) { return 0; } virtual int GetRectRihgt(void) { return rcParent.right; } virtual int GetRectBottom(void) { return 26; }
// Определение кисти для окна TOOLBAR virtual HBRUSH GetBrush(void) { return GetStockBrush(LTGRAY_BRUSH);
}
// Определение расположения кнопки исходя из ее номера virtual int GetBtnX(int nPosition) { return 3 + (nPosition * 30);
}
virtual int GetBtnY(int nPosition) { return 3; }
// Определение размеров кнопки virtual int GetBmpWidth(void) { return 30; } virtual int GetBmpHeigt(void) { return 20; } };
В этом файле определен также класс TbMain, все члены которого описаны как статические. Такой класс можно использовать вместо набора глобальных переменных, что улучшает структуру программы.
В классе TbMain хранится идентификатор приложения hInst, три массива, в которых хранятся идентификаторы изображений bitmap для кнопок в исходном (hbmpUp), нажатом (hbmpDown) и заблокированном (hbmpGrayed) состоянии, идентификатор родительского окна, на поверхности которого создается Toolbar, идентификатор самой левой кнопки в окне Toolbar (nFirstId). Все перечисленные выше переменные должны быть доступны для методов класса Toolbar и для функции окна Toolbar.
В классе Toolbar определены переменные, предназначенные для хранения размеров родительского окна и окна Toolbar, имени класса Toolbar, идентификаторов кнопок, кода ошибки.
Метод GetRect предназначен для определения размеров дочернего окна Toolbar, на поверхности которого создаются кнопки. Этот метод вызывает функции GetRectLeft, GetRectTop, GetRectRight, GetRectBottom, определяющие соответственно расположение левой, верхней, правой и нижней границ дочернего окна.
Вы можете создать свой класс как дочерний для класса Toolbar и переопределить все или некоторые из перечисленных функций, создав, например, вертикальный Toolbar.
Метод RegisterWndClass регистрирует класс для дочернего окна Toolbar. Этот метод использует функцию GetBrush для определения кисти, используемой для закраски поверхности дочернего окна. В классе Toolbar используется светло-серая кисть. Создавая собственный класс на базе класса Toolbar вы можете переопределить функцию GetBrush и закрасить поверхность дочернего окна в любой цвет.
Метод CreateTbWindow создает дочернее окно Toolbar. Перед вызовом этого метода необходимо с помощью метода GetRect определить размеры и расположение дочернего окна органа управления Toolbar.
Для того чтобы при изменении размеров основного окна обеспечить соответствующее изменение размеров окна Toolbar, в классе Toolbar определен метод SetWidth. Единственный параметр nWidth должен содержать значение новой ширины окна. Метод SetWidth необходимо вызывать из функции главного окна приложения (или другого окна, на поверхности которого расположен Toolbar) при обработке сообщения WM_SIZE.
Если вы создаете вертикальный Toolbar, вы можете определить в своем классе, созданном на базе класса Toolbar, функцию SetHeight, изменяющую высоту дочернего окна Toolbar.
Для вставки в Toolbar кнопки вы должны вызвать метод InsertButton.
Параметр NPosition определяет расположение кнопки на поверхности Toolbar и идентификатор кнопки, передаваемый в родительское окно через параметр wParam сообщения WM_COMMAND. Идентификатор кнопки зависит от расположения кнопки в окне Toolbar и определяется при помощи следующего выражения:
nPosition + TbMain::nFirstId
Таким образом, самой левой кнопке в горизонтальном органе управления Toolbar соответствует идентификатор TbMain::nFirstId, значение которого вы задаете при вызове конструктора класса Toolbar:
Toolbar( HINSTANCE hInst, HWND hwndParent, int nFirstid);
Дополнительно вы должны указать конструктору идентификатор текущей копии приложения hInst и идентификатор родительского окна, на поверхности которого расположен Toolbar. Определение конструктора и деструктора класса Toolbar находится в файле toolbar.cpp (листинг 1.13).
Среди других методов, определенных в классе Toolbar, отметим метод GetBtnX. Этот метод используется для вычисления X-координаты кнопки в окне Toolbar в зависимости от ее номера. Если вы создаете свой Toolbar на базе класса Toolbar, вы можете использовать другой алгоритм размещения кнопок, переопределив эту функцию, а также функцию GetBtnY, предназначенную для вычисления Y-координаты кнопки.
По умолчанию для кнопок используются изображения bitmap размером 30 х 20 пикселов. Если вам нужны кнопки другого размера, переопределите методы GetBmpWidth и GetBmpHeight.
В файле toolbar.cpp (листинг 1.13) находятся определения некоторых методов класса Toolbar, членов класса TbMain и функции дочернего окна Toolbar.
Файл smartpad/toolbar.cpp
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <mem.h>
#include "toolbar.hpp"
LRESULT CALLBACK _export ToolbarWndProc(HWND, UINT, WPARAM, LPARAM);
void DrawButton(LPDRAWITEMSTRUCT lpInfo);
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap);
// Определяем члены статического класса TbMain HBITMAP TbMain::hbmpUp[20]; HBITMAP TbMain::hbmpDown[20]; HBITMAP TbMain::hbmpGrayed[20]; HWND TbMain::hwndParent; HINSTANCE TbMain::hInst; int TbMain::nFirstId = 0;
// ====================================================== // Конструктор класса Toolbar // ====================================================== Toolbar::Toolbar(HINSTANCE hInstance, HWND hwndParentWindow, int nFirstId) { // Сбрасываем признак ошибки errno = 0;
// Сохраняем идентификатор копии приложения, // идентификатор родительского окна, создавшего // TOOLBAR, и идентификатор первой кнопки в // органе управления TOOLBAR TbMain::hInst = hInstance; TbMain::hwndParent = hwndParentWindow; TbMain::nFirstId = nFirstId;
// Определяем размеры внутренней области // родительского окна GetClientRect(TbMain::hwndParent, &rcParent);
// Определяем размеры дочернего окна, которое // будет использовано для создания органа TOOLBAR GetRect();
// Регистрируем класс для дочернего окна TOOLBAR if(!RegisterWndClass()) { errno = 1; return; }
// Создаем дочернее окно TOOLBAR CreateTbWindow();
if(hwndToolbar == NULL) { errno = 2; return; } }
// ====================================================== // Деструктор класса Toolbar // ====================================================== Toolbar::~Toolbar() { // Уничтожаем дочернее окно TOOLBAR DestroyWindow(hwndToolbar);
}
// ====================================================== // Функция окна ToolbarWndProc для дочернего окна TOOLBAR // ======================================================
LRESULT CALLBACK _export ToolbarWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // Передаем сообщение WM_COMMAND от кнопок в // родительское окно case WM_COMMAND: { SendMessage(TbMain::hwndParent, WM_COMMAND, wParam, lParam);
return 0; }
// Это сообщение приходит при изменении состояния // дочернего окна органа управления, когда окно // нужно перерисовать case WM_DRAWITEM: { // Перерисовываем кнопку DrawButton( (LPDRAWITEMSTRUCT)lParam );
break; } } return DefWindowProc(hwnd, msg, wParam, lParam);
}
// ====================================================== // Функция DrawButton // Перерисовывает кнопку // ====================================================== void DrawButton(LPDRAWITEMSTRUCT lpInfo) { HBITMAP hbm;
// Обрабатываем сообщение WM_DRAWITEM // только если оно поступило от кнопки if(lpInfo->
CtlType != ODT_BUTTON) return;
hbm = TbMain::hbmpUp[(lpInfo->
CtlID) - TbMain::nFirstId];
// Если кнопка выбрана, рисуем ее в нажатом // состоянии if (lpInfo->
itemState & ODS_SELECTED) { hbm = TbMain::hbmpDown[(lpInfo->
CtlID) - TbMain::nFirstId]; }
// Если кнопка неактивна, загружаем идентификатор // изображения кнопки в неактивном состоянии else if (lpInfo->
itemState & ODS_DISABLED) { hbm = TbMain::hbmpGrayed[(lpInfo->
CtlID) - TbMain::nFirstId]; }
// При ошибке ничего не рисуем if(!hbm) return;
// Если кнопка выбрана и ее надо целиком // перерисовать, вызываем функцию DrawBitmap if((lpInfo->
itemAction & ODA_DRAWENTIRE) (lpInfo->
itemAction & ODA_SELECT)) { // Рисуем кнопку DrawBitmap(lpInfo->
hDC, (lpInfo->
rcItem).left, (lpInfo->
rcItem).top , hbm);
} }
Конструктор класса Toolbar сбрасывает признак ошибки errno, инициализирует члены класса TbMain, определяет размеры внутренней области родительского окна, размеры дочернего окна, регистрирует класс дочернего окна и создает само дочернее окно.
Работа деструктор заключается в уничтожении дочернего окна, для чего используется функция DestroyWindow.
Функция дочернего окна ToolbarWndProc обрабатывает сообщение WM_COMMAND, поступающее от кнопок, и формирует это же сообщение для дочернего окна. Кроме того, эта функция обеспечивает работу кнопок, для чего в ней предусмотрены обработчик сообщения WM_DRAWITEM.Этот обработчик вызывает функцию DrawButton, предназначенную для рисования кнопок.
Функция DrawButton рисует кнопку в нужном виде, выбирая идентификаторы соответствующих изображений bitmap из массивов, определенных в классе TbMain. Для рисования изображения вызывается функция DrawBitmap (листинг 1.14). Мы уже пользовались этой функцией.
Файл smartpad/drawbmp.cpp
// ====================================================== // Рисование изображения типа bitmap // ======================================================
#define STRICT #include <windows.h>
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg;
// Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC);
// Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
// Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC));
// Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота
// Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1);
ptOrg.x = 0; ptOrg.y = 0;
// Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1);
// Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
// Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm);
}
// Удаляем контекст памяти DeleteDC(hMemDC);
}
Файл определения модуля для приложения SMARTPAD приведен в листинге 1.15.
Файл smartpad/smartpad.def
; ============================= ; Файл определения модуля ; ============================= NAME SMARTPAD DESCRIPTION 'Приложение SMARTPAD, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл gmenu/gmenu.cpp
// ---------------------------------------------- // Использование графических изображений в меню // ----------------------------------------------
#define STRICT #include <windows.h>
#include <mem.h>
#include "gmenu.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "GMenuClass";
// Заголовок окна char const szWindowTitle[] = "Menu Demo";
// Идентификатор меню верхнего уровня HMENU hmenu;
// Идентификаторы временных меню HMENU hmenuFile; // "File" HMENU hmenuLineStyle; // "Edit" HMENU hmenuHelp; // "Help"
// Идентификаторы графических изображений для строк меню HBITMAP hbmpLine1; HBITMAP hbmpLine2; HBITMAP hbmpLine3; HBITMAP hbmpLine4; HBITMAP hbmpLineStyle; HBITMAP hbmpChecked; HBITMAP hbmpUnchecked;
// Идентификатор текущей копии приложения HINSTANCE hInst;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор текущей копии приложения hInst = hInstance;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: { // Загружаем изображения строк меню hbmpLine1 = LoadBitmap(hInst, "LINE1");
hbmpLine2 = LoadBitmap(hInst, "LINE2");
hbmpLine3 = LoadBitmap(hInst, "LINE3");
hbmpLine4 = LoadBitmap(hInst, "LINE4");
hbmpLineStyle = LoadBitmap(hInst, "LINESTYLE");
hbmpChecked = LoadBitmap(hInst, "CHECKED");
hbmpUnchecked = LoadBitmap(hInst, "UNCHECKED");
// Создаем пустое меню верхнего уровня hmenu = CreateMenu();
// Подключаем меню к главному окну приложения SetMenu(hwnd, hmenu);
// Создаем временные меню hmenuFile = CreatePopupMenu();
hmenuHelp = CreatePopupMenu();
hmenuLineStyle = CreatePopupMenu();
// Добавляем строки к меню "File" AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILENEW, "&New");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEOPEN, "&Open");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILECLOSE, "&Close");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILESAVE, "&Save");
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILESAVEAS, "Save &as...");
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuFile, MF_DISABLED | MF_STRING, CM_FILEDEMO, "&Demo Version");
AppendMenu(hmenuFile, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuFile, MF_ENABLED | MF_STRING, CM_FILEEXIT, "E&xit");
// Для строки "Demo Version" меню "File" определяем // изображения, которые будут использоваться для // вывода строки в отмеченном и неотмеченном состоянии SetMenuItemBitmaps(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND, hbmpUnchecked, hbmpChecked);
// Отмечаем строку "Demo Version" CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
// Добавляем строки к меню "Line Style". // Вместо текстовых строк используем графические // изображения bitmap AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE1, (LPCSTR)(DWORD)hbmpLine1);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE2, (LPCSTR)(DWORD)hbmpLine2);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE3, (LPCSTR)(DWORD)hbmpLine3);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE4, (LPCSTR)(DWORD)hbmpLine4);
// Добавляем строки к меню "Help" AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPINDEX, "&Index\tF1");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPKEYBOARD, "&Keyboard");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPCOMMANDS, "&Commands");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPPROCEDURES, "&Procedures");
AppendMenu(hmenuHelp, MF_GRAYED | MF_STRING, CM_HELPUSING_HELP, "&Using help");
AppendMenu(hmenuHelp, MF_SEPARATOR, 0, NULL);
AppendMenu(hmenuHelp, MF_ENABLED | MF_STRING, CM_HELPABOUT, "&About...");
// Добавляем временные меню к меню верхнего уровня AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuFile, "&File");
// Для временного меню "Line Style" используем // изображение bitmap AppendMenu(hmenu, MF_ENABLED | MF_POPUP | MF_BITMAP, (UINT)hmenuLineStyle, (LPCSTR)(DWORD)hbmpLineStyle);
AppendMenu(hmenu, MF_ENABLED | MF_POPUP, (UINT)hmenuHelp, "&Help");
// Перерисовываем меню DrawMenuBar(hwnd);
return 0; }
case WM_COMMAND: { switch (wParam) { // Сообщения от меню case CM_HELPUSING_HELP: case CM_HELPPROCEDURES: case CM_HELPCOMMANDS: case CM_HELPKEYBOARD: case CM_HELPINDEX: case CM_FILESAVEAS: case CM_FILESAVE: case CM_FILEOPEN: case CM_FILENEW: case CM_FILECLOSE: { // Выводим сообщение об ошибке MessageBox(hwnd, "Функция не реализована", NULL, MB_OK);
return 0; }
// Выбрали строку "About..." в меню "Help" case CM_HELPABOUT: { MessageBox(hwnd, "Приложение GMENU\n(C) Фролов А.В., 1994", szWindowTitle, MB_OK | MB_ICONINFORMATION);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } default: return 0; } }
case WM_DESTROY: { // Уничтожаем созданные ранее меню DestroyMenu(hmenuFile);
DestroyMenu(hmenuHelp);
DestroyMenu(hmenuLineStyle);
DestroyMenu(hmenu);
// Удаляем изображения DeleteObject(hbmpLine1);
DeleteObject(hbmpLine2);
DeleteObject(hbmpLine3);
DeleteObject(hbmpLine4);
DeleteObject(hbmpLineStyle);
DeleteObject(hbmpChecked);
DeleteObject(hbmpUnchecked);
PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}
При обработке сообщения WM_CREATE приложение, наряду с другими инициализирующими действиями, загружает из ресурсов приложения все необходимые для меню изображения bitmap:
hbmpLine1 = LoadBitmap(hInst, "LINE1");
hbmpLine2 = LoadBitmap(hInst, "LINE2");
hbmpLine3 = LoadBitmap(hInst, "LINE3");
hbmpLine4 = LoadBitmap(hInst, "LINE4");
hbmpLineStyle = LoadBitmap(hInst, "LINESTYLE");
hbmpChecked = LoadBitmap(hInst, "CHECKED");
hbmpUnchecked = LoadBitmap(hInst, "UNCHECKED");
Для строки "Demo Version" мы используем созданные нами и описанные в ресурсах приложения изображения bitmap, для чего вызываем функцию SetMenuItemBitmaps :
SetMenuItemBitmaps(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND, hbmpUnchecked, hbmpChecked);
Далее мы отмечаем указанную строку:
CheckMenuItem(hmenuFile, CM_FILEDEMO, MF_BYCOMMAND | MF_CHECKED);
Затем мы переходим к формированию временного меню, содержащего графические изображения. Для этого в меню hmenuLineStyle, созданное ранее как пустое, мы добавляем четыре строки:
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE1, (LPCSTR)(DWORD)hbmpLine1);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE2, (LPCSTR)(DWORD)hbmpLine2);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE3, (LPCSTR)(DWORD)hbmpLine3);
AppendMenu(hmenuLineStyle, MF_ENABLED | MF_BITMAP, CM_LINE4, (LPCSTR)(DWORD)hbmpLine4);
Как видно из рис. 1.21, для строки "Line Style" используется нестандартный шрифт. Точнее говоря, для этой строки мы использовали изображение bitmap, на котором написаны слова "Line Style":
AppendMenu(hmenu, MF_ENABLED | MF_POPUP | MF_BITMAP, (UINT)hmenuLineStyle, (LPCSTR)(DWORD)hbmpLineStyle);
Перед завершением работы приложения мы удаляем все загруженные изображения bitmap для освобождения системных ресурсов:
DeleteObject(hbmpLine1);
DeleteObject(hbmpLine2);
DeleteObject(hbmpLine3);
DeleteObject(hbmpLine4);
DeleteObject(hbmpLineStyle);
DeleteObject(hbmpChecked);
DeleteObject(hbmpUnchecked);
Символические константы, использованные в приложении GMENU, описаны в файле gmenu.cpp (листинг 1.17).
Файл gmenu/gmenu.hpp
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341
#define CM_FILEEXIT 24338 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331 #define CM_FILECLOSE 24330 #define CM_FILEDEMO 24329
#define CM_LINE1 100 #define CM_LINE2 101 #define CM_LINE3 102 #define CM_LINE4 103
Файл описания ресурсов приложения GMENU приведен в листинге 1.18. В нем находятся ссылки на изображения bitmap, используемые для отображения строк меню.
Файл gmenu/gmenu.rc
LINE1 BITMAP "line1.bmp" LINE2 BITMAP "line2.bmp" LINE3 BITMAP "line3.bmp" LINE4 BITMAP "line4.bmp" CHECKED BITMAP "checked.bmp" UNCHECKED BITMAP "uncheck.bmp" LINESTYLE BITMAP "linestyl.bmp"
В листинге 1.19 приведен внешний вид этих изображений.
Файлы gmenu/*.bmp
linestyl.bmp | |
line2.bmp | |
line3.bmp | |
line3.bmp | |
line4.bmp | |
checked.bmp | |
uncheck.bmp |
Файл определения модуля приложения GMENU приведен в листинге 1.20.
Файл gmenu/gmenu.def
; ============================= ; Файл определения модуля ; ============================= NAME GMENU DESCRIPTION 'Приложение GMENU, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл selector/selector.cpp
#define STRICT #include <windows.h>
#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { UINT uSelCS, uSelDS, uTICS, uTIDS; BYTE szBuf[100];
// Получаем селектор сегмента кода asm mov ax, cs asm mov uSelCS, ax
// Получаем селектор сегмента данных asm mov ax, ds asm mov uSelDS, ax
// Выделяем бит TI. Если этот бит // равен 1, для адресации используется // глобальная таблица дескрипторов, // если 0 - локальная uTICS = (uSelCS & 4) >
>
2; uTIDS = (uSelDS & 4) >
>
2;
// Выводим значения селекторов для сегментов // кода и данных, значения поля TI и номер // кольца защиты wsprintf(szBuf, "CS=%0X \tTI=%d\tRING=%d" "\nDS=%0X \tTI=%d\tRING=%d", uSelCS, uTICS, uSelCS & 3, uSelDS, uTIDS, uSelDS & 3);
MessageBox(NULL, (LPSTR)szBuf, "CS & DS selector's", MB_OK);
return 0; }
Это приложение переписывает текущее содержимое регистров процессора CS и DS в переменные uSelCS и uSelDS. Далее содержимое бита TI селекторов, взятых из регистров DS и CS, переписывается в переменные uTICS и uTIDS, соответственно.
Запустив это нехитрое приложение, вы сможете убедиться, что операционная система Windows версии 3.1 предоставляет приложениям самый низкий уровень привилегий, располагая их в третьем кольце защиты (рис. 2.9).
Рис. 2.9. Содержимое регистров CS и DS
Так как содержимое поля TI равно 1, это означает, что для адресации сегмента кода и сегмента данных используется локальная таблица дескрипторов.
В операционной системе Windows версии 3.1 все приложения используют одну общую локальную таблицу дескрипторов, что в принципе не исключает взаимного влияния приложений. Это означает, что адресные пространства приложений Windows не изолированы друг от друга. Поэтому не пытайтесь загружать в сегментные регистры неправильные селекторы. В следующих версиях Windows каждое приложение будет иметь свое собственное адресное пространство.
Используйте только те селекторы, которые получены приложением от операционной системы Windows.
Файл определения модуля приложения SELECTOR ничем не примечателен и приведен в листинге 2.2.
Файл selector/selector.def
; ============================= ; Файл определения модуля ; ============================= NAME SELECTOR DESCRIPTION 'Приложение SELECTOR, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл gmem/gmem.cpp
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <dos.h>
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[100]; HGLOBAL hmemGlobal; HGLOBAL hmemGlDiscard; LPVOID lpvoidGlobal; LPVOID lpvoidGlDiscard; DWORD dwMaxFreeMem;
// Определяем размер доступной памяти dwMaxFreeMem = GlobalCompact(-1l);
wsprintf(szBuf, "Доступно памяти:\t%lu\n", dwMaxFreeMem);
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// -------------------------------------------------------- // Работаем с перемещаемым блоком памяти // --------------------------------------------------------
// Дефрагментируем память для получения блока // размером 100000 байт dwMaxFreeMem = GlobalCompact(100000l);
// Заказываем буфер размером 100000 байт hmemGlobal = GlobalAlloc(GHND, 100000l);
if(hmemGlobal != NULL) { // Если буфер получен, фиксируем его в памяти lpvoidGlobal = GlobalLock(hmemGlobal);
if(lpvoidGlobal != (LPVOID) NULL) { // Если блок успешно зафиксирован, // выводим значения идентификатора блока // и логический адрес блока wsprintf(szBuf, "hmemGlobal=\t%04.4X\n" "lpvoidGlobal=\t%04.4X:%04.4X", hmemGlobal, FP_SEG(lpvoidGlobal), FP_OFF(lpvoidGlobal));
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// ----------------------------------------- // Можно работать с полученным блоком памяти // Записываем в первый байт блока символ S *(LPSTR)lpvoidGlobal = 'S'; // -----------------------------------------
// Разрешаем перемещение блока GlobalUnlock(hmemGlobal);
} else { MessageBox(NULL, "Ошибка при фиксировании блока", "Global Block", MB_OK);
} // Отдаем блок памяти операционной системе GlobalFree(hmemGlobal);
} else { MessageBox(NULL, "Мало памяти для перемещаемого блока", "Global Block", MB_OK);
}
// -------------------------------------------------------- // Работаем с удаляемым блоком памяти // --------------------------------------------------------
// Заказываем удаляемый блок памяти размером 200000 байт hmemGlDiscard = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l);
if(hmemGlDiscard != NULL) { // Если мы его получили, удаляем блок GlobalDiscard(hmemGlDiscard);
// Пытаемся зафиксировать блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard);
if(lpvoidGlDiscard != (LPVOID) NULL) { // Если удалось (чего не должно быть, так как // мы только что удалили блок), выводим // идентификатор блока и логический адрес wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// Разрешаем перемещение блока GlobalUnlock(hmemGlDiscard);
} else { // Если блок памяти не удалось зафиксировать, // проверяем, не был ли он удален if(GlobalFlags(hmemGlDiscard) & GMEM_DISCARDED) { MessageBox(NULL, "Блок удален и мы его восстанавливаем", "Global Block", MB_OK);
// Восстанавливаем удаленный блок памяти hmemGlDiscard = GlobalReAlloc(hmemGlDiscard, 200000l, GMEM_MOVEABLE | GMEM_DISCARDABLE);
// Фиксируем блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard);
if(lpvoidGlDiscard != (LPVOID) NULL) { // Выводим идентификатор и логический адрес // зафиксированного блока памяти wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// Освобождаем блок памяти GlobalUnlock(hmemGlDiscard);
} else { MessageBox(NULL, "Ошибка при фиксировании блока", "Global Block", MB_OK);
} } }
// Отдаем удаляемый блок памяти операционной системе GlobalFree(hmemGlDiscard);
} else { MessageBox(NULL, "Мало памяти для удаляемого блока", "Global Block", MB_OK);
}
return 0; }
Перед началом работы приложение определяет объем свободной памяти, вызывая функцию GlobalCompact со значением параметра, равным -1.
Определенное значение выводится на экран при помощи функции MessageBox.
Далее приложение вызывает функцию GlobalCompact еще раз для освобождения непрерывного блока свободной памяти размером 100000 байт.
После этого приложение заказывает буфер размером 100000 байт, вызывая функцию GlobalAlloc. В качестве первого параметра этой функции указана константа GHND, соответствующее перемещаемой памяти, инициализированной нулевым значением.
В случае успешного получения блока памяти он фиксируется и на экран выводится значение идентификатора блока и его логический адрес. После этого в первый байт полученного блока записывается код символа 'S' и блок расфиксируется. При ошибке выдается сообщение.
Далее полученный блок освобождается и возвращается операционной системе, для чего вызывается функция GlobalFree.
После этого приложение заказывает удаляемый блок памяти размером 200000 байт и сразу же удаляет его, вызывая макрокоманду GlobalDiscard. Затем приложение предпринимает попытку зафиксировать только что удаленный блок памяти, вызывая функцию GlobalLock. Если блок памяти удалился успешно, функция GlobalLock должна вернуть значение NULL. В этом случае нам надо убедиться в том, что блок был удален, и если это так и есть, восстановить удаленный блок.
Для проверки приложение использует функцию GlobalFlags. Если блок, идентификатор которого был передан этой функции в качестве параметра, удален, в возвращаемом значении установлен флаг GMEM_DISCARDED.
Для восстановления удаленного блока приложение вызывает функцию GlobalReAlloc, указывая размер и характеристики блока. Затем восстановленный блок фиксируется в памяти и на экран выводится его идентификатор и логический адрес.
Перед завершением работы приложения блок расфиксируется и освобождается.
Файл определения модуля приложения GMEM приведен в листинге 2.4.
Файл gmem/gmem.def
; ============================= ; Файл определения модуля ; ============================= NAME GMEM DESCRIPTION 'Приложение GMEM, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' CODE preload moveable discardable DATA preload moveable multiple
Файл lmem/lmem.cpp
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <dos.h>
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[100]; HLOCAL hmemLocal; HLOCAL hmemLoDiscard; void* pLocal; void* pLoDiscard; UINT uMaxFreeMem;
// Определяем размер доступной памяти uMaxFreeMem = LocalCompact(0);
wsprintf(szBuf, "Доступно памяти:\t%u\n", uMaxFreeMem);
MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);
// -------------------------------------------------------- // Работаем с перемещаемым блоком памяти // --------------------------------------------------------
// Дефрагментируем память для получения блока // размером 1000 байт uMaxFreeMem = LocalCompact(1000);
// Заказываем буфер размером 1000 байт hmemLocal = LocalAlloc(GHND, 1000);
if(hmemLocal != NULL) { // Если буфер получен, фиксируем его в памяти pLocal = LocalLock(hmemLocal);
if(pLocal != NULL) { // Если блок успешно зафиксирован, // выводим значения идентификатора блока // и логический адрес блока wsprintf(szBuf, "hmemLocal=\t%04.4X\n" "pLocal=\t\t%04.4X", hmemLocal, pLocal);
MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);
// ----------------------------------------- // Можно работать с полученным блоком памяти // Записываем в первый байт блока символ S *(PSTR)pLocal = 'S'; // -----------------------------------------
// Разрешаем перемещение блока LocalUnlock(hmemLocal);
} else { MessageBox(NULL, "Ошибка при фиксировании блока", "Local Block", MB_OK);
} // Отдаем блок памяти операционной системе LocalFree(hmemLocal);
} else { MessageBox(NULL, "Мало памяти для перемещаемого блока", "Local Block", MB_OK);
}
// -------------------------------------------------------- // Работаем с удаляемым блоком памяти // --------------------------------------------------------
// Заказываем удаляемый блок памяти размером 2000 байт hmemLoDiscard = LocalAlloc(LMEM_MOVEABLE | LMEM_DISCARDABLE, 2000);
if(hmemLoDiscard != NULL) { // Если мы его получили, удаляем блок LocalDiscard(hmemLoDiscard);
// Пытаемся зафиксировать блок памяти pLoDiscard = LocalLock(hmemLoDiscard);
if(pLoDiscard != NULL) { wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n" "pLoDiscard=\t%04.4X", hmemLoDiscard, pLoDiscard);
MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);
// Разрешаем перемещение блока LocalUnlock(hmemLoDiscard);
} else { // Если блок памяти не удалось зафиксировать, // проверяем, не был ли он удален if(LocalFlags(hmemLoDiscard) & LMEM_DISCARDED) { MessageBox(NULL, "Блок удален и мы его восстанавливаем", "Local Block", MB_OK);
// Восстанавливаем удаленный блок памяти hmemLoDiscard = LocalReAlloc(hmemLoDiscard, 256, LMEM_MOVEABLE | LMEM_DISCARDABLE);
// Фиксируем блок памяти pLoDiscard = LocalLock(hmemLoDiscard);
if(pLoDiscard != NULL) { // Выводим идентификатор и логический адрес // зафиксированного блока памяти wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n" "pLoDiscard=\t%04.4X", hmemLoDiscard, pLoDiscard);
MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK);
// Освобождаем блок памяти LocalUnlock(hmemLoDiscard);
} else { MessageBox(NULL, "Ошибка при фиксировании блока", "Local Block", MB_OK);
} } }
// Отдаем удаляемый блок памяти операционной системе LocalFree(hmemLoDiscard);
} else { MessageBox(NULL, "Мало памяти для удаляемого блока", "Local Block", MB_OK);
}
return 0; }
Файл определения модуля приложения LMEM приведен в листинге 2.6.
Файл lmem/lmem.def
; ============================= ; Файл определения модуля ; ============================= NAME LMEM DESCRIPTION 'Приложение LMEM, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 4096 CODE preload moveable discardable DATA preload moveable multiple
В этом файле начальное значение локальной области данных установлено равным 4096 байт. Проверяя работу приложения LMEM, вы можете попробовать уменьшить размер локальной области данных, например, до величины 1 Кбайт, а затем заказать локальный блок памяти размером 10 Кбайт. В этом случае несмотря на то, что сразу после запуска приложения в локальной области данных будет свободно всего несколько сотен байт, запрос на 10 Кбайт будет удовлетворен за счет автоматического увеличения размера локальной области данных.
Файл dllcall/dllsrc.cpp
#define STRICT #include <windows.h>
// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ========================================================
#pragma argsused int FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать. if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0);
// Возвращаем 1. Это означает, что инициализация // DLL-библиотеки выполнена успешно return 1; }
// ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ========================================================
#pragma argsused int FAR PASCAL WEP(int bSystemExit) { return 1; }
// ======================================================== // Функция Msg // Выводит на экран диалоговую панель с сообщением // ========================================================
void FAR PASCAL _export Msg(LPSTR lpszMsg) { MessageBox(NULL, lpszMsg, "DLLSRC", MB_OK);
}
Функция LibMain проверяет размер локальной области данных, заказанной для DLL-приложения функцией LibEntry. Этот размер определяется значением, указанным в файле определения модуля DLL-библиотеки при помощи оператора HEAPSIZE и передается функции LibMain через параметр wHeapSize. Если DLL-библиотека имеет локальную область данных, на этапе инициализации ее необходимо расфиксировать для того чтобы разрешить Windows при необходимости произвольно изменять логический адрес сегмента данных в процессе перемещения сегментов. Использованный способ расфиксирования сегмента данных был описан в предыдущей главе и основан на использовании макрокоманды UnlockData.
После инициализации, которая в нашем простейшем примере всегда выполняется успешно, функция WinMain возвращает признак успешной инициализации - значение 1.
Задача функции WEP в нашем случае сводится к возврату значения 1, означающего успешное завершение.
Функция Msg - единственная функция предназначена для вывода строки сообщения, адрес которой передается ей в качестве параметра. Так как эта функция определена с ключевым словом _export, она может быть вызвана любым приложением. Именно эту функцию мы будем вызывать из приложения DLLCALL, исходные тексты которого приведены ниже.
Файл определения модуля для DLL-библиотеки имеет некоторые особенности (листинг 3.2).
Файл dllcall/dll.def
; ============================= ; Файл определения модуля ; ============================= LIBRARY DLLSRC DESCRIPTION 'DLL-библиотека DLLSRC, (C) 1994, Frolov A.V.' EXETYPE windows CODE preload moveable discardable DATA preload moveable single HEAPSIZE 1024
Во-первых, в нем нет оператора NAME, предназначенного для идентификации приложения. Вместо него используется оператор LIBRARY. Этот оператор указывает, что данный файл описывает не приложение, а DLL-библиотеку.
Во-вторых, в операторе DATA указан параметр single, так как в памяти может находиться только одна копия DLL-библиотеки и, соответственно, может существовать только один сегмент данных.
И в-третьих, в файле определения модуля нет оператора STACKSIZE, так как DLL-библиотека не может иметь стек.
Для создания библиотеки вы можете воспользоваться файлом dllsrc.prj, который есть на дискете, продаваемой вместе с книгой в каталоге dllcall.
В файла проекта DLL-библиотеки, сделанной с помощью системы разработки Borland Turbo C++ for Windows, вам необходимо указать правильный тип приложения. Для этого в меню "Options" выберите строку "Application..." и в появившейся не экране диалоговой панели "Application Options" нажмите на кнопку "Windows DLL" (рис. 3.7). После этого нажмите на кнопку "OK".
Рис. 3.7. Диалоговая панель "Application Options"
Обратите внимание на то, что по умолчанию при создании DLL-библиотеки все функции, определенные в ней, становятся экспортируемыми (Windows DLL all functions exportable). Это означает, что все функции, определенные вами в DLL-библиотеке, имеют специальный пролог и эпилог, предназначенный для правильной загрузки регистра DS (в этот регистр записывается селектор сегмента данных DLL-библиотеки). Кроме того, все эти функции перечисляются в заголовке загрузочного dll-файла библиотеки.
По умолчанию для DLL-библиотеки используется компактная модель данных. В этой модели существует один сегмент кода и несколько сегментов данных. Кроме того, для ссылки на данные всегда используются дальние указатели, состоящие из селектора и смещения. Последнее обстоятельство очень важно. Вспомним, что при вызове функции из DLL-библиотеки регистр DS указывает на локальный сегмент данных библиотеки, а регистр SS - на сегмент данных вызывающего приложения. Так как трансляторы языка С передают параметры через стек, а также используют стек для размещения локальных переменных, вы не сможете адресовать параметры и локальные переменные через регистр DS (он указывает на сегмент данных DLL-библиотеки, а параметры и локальные переменные находятся в сегменте данных приложения). Очевидный выход - использование компактной или большой модели памяти, в которых используются дальние указатели на переменные.
Исходный текст приложения DLLCALL, вызывающего функцию Msg из нашей DLL-библиотеки, приведен в листинге 3.3.
Файл dllcall/dllcall.cpp
// ---------------------------------------- // Вызов функции, расположенной в // DLL-библиотеке // ----------------------------------------
#define STRICT #include <windows.h>
// Объявление функции, расположенной в DLL-библиотеке extern void FAR PASCAL Msg(LPSTR lpszMsg);
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Вызываем функцию из DLL-библиотеки Msg((LPSTR)"Вам привет из DLL!");
return 0; }
Обратите внимание на то, что функция Msg описана как внешняя:
extern void FAR PASCAL Msg(LPSTR lpszMsg);
Файл определения модуля для приложения DLLCALL приведен в листинге 3.4. Он не имеет никаких особенностей.
Файл dllcall/dllcall.def
; ============================= ; Файл определения модуля ; ============================= NAME DLLCALL DESCRIPTION 'Приложение DLLCALL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Файл проекта приложения DLLCALL включает в себя библиотеку импорта dllsrc.lib, которую мы сделали из DLL-библиотеки dllsrc.dll при помощи приложения Import Lib, входящего в состав системы разработки Borland Turbo C++ for Windows.
Файл discard/discard.cpp
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <dos.h>
// Прототип функции извещения о том, что Windows // планирует удалить блок памяти // Так как функция NotifyProc составлена на языке // программирования C (а не C++), мы описываем ее // как extern "C" extern "C" BOOL CALLBACK _export NotifyProc(HGLOBAL hglbl);
#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[100]; HGLOBAL hmemGlDiscard; LPVOID lpvoidGlobal; LPVOID lpvoidGlDiscard; DWORD dwMaxFreeMem;
// Определяем размер доступной памяти dwMaxFreeMem = GlobalCompact(-1l);
wsprintf(szBuf, "Доступно памяти:\t%lu\n", dwMaxFreeMem);
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// Устанавливаем процедуру извещения, которая // получит управление при попытке удалить // блок памяти GlobalNotify((GNOTIFYPROC)NotifyProc);
// Заказываем удаляемый блок памяти размером 200000 байт // Для включения режима извещения необходимо // указать флаг GMEM_NOTIFY hmemGlDiscard = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE | GMEM_NOTIFY, 200000l);
if(hmemGlDiscard != NULL) { // Если мы его получили, пытаемся удалить блок GlobalDiscard(hmemGlDiscard);
// Фиксируем блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard);
if(lpvoidGlDiscard != (LPVOID) NULL) { // Так как наша процедура извещения запрещает // удаление блока, попытка его фиксирования // должна закончится успешно. В этом случае // мы выводим идентификатор блока памяти и его // логический адрес wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// Разрешаем перемещение блока GlobalUnlock(hmemGlDiscard);
} else { // Если блок памяти не удалось зафиксировать, // проверяем, не был ли он удален if(GlobalFlags(hmemGlDiscard) & GMEM_DISCARDED) { // Так как мы запретили удаление блока, следующее // сообщение не должно появиться на экране MessageBox(NULL, "Блок удален и мы его" " восстанавливаем", "Global Block", MB_OK);
// Восстанавливаем удаленный блок памяти hmemGlDiscard = GlobalReAlloc(hmemGlDiscard, 200000l, GMEM_MOVEABLE | GMEM_DISCARDABLE);
// Фиксируем блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard);
if(lpvoidGlDiscard != (LPVOID) NULL) { // Выводим идентификатор и логический адрес // зафиксированного блока памяти wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);
// Освобождаем блок памяти GlobalUnlock(hmemGlDiscard);
} else { MessageBox(NULL, "Ошибка при фиксировании блока", "Global Block", MB_OK);
} } }
// Отдаем удаляемый блок памяти операционной системе GlobalFree(hmemGlDiscard);
} else { MessageBox(NULL, "Мало памяти для удаляемого блока", "Global Block", MB_OK);
} return 0; }
DLL-библиотека, в которой расположена функция извещения, составлена нами с использованием языка программирования C, а не C++. Это сделано для упрощения экспортирования функции, которая в данном случае экспортируется с использованием своего порядкового номера. Подробнее этот вопрос мы рассмотрим при описании исходного текста DLL-библиотеки, а пока заметим, что так как для функции извещения использован язык C, мы должны это отметить в прототипе функции, включаемой в исходный текст приложения, составленного на языке C++:
extern "C" BOOL CALLBACK _export NotifyProc(HGLOBAL hglbl);
После запуска приложения функция WinMain определяет размер доступной памяти, вызывая функцию GlobalCompact, а затем выводит его на экран.
После этого функция WinMain устанавливает процедуру извещения, вызывая функцию GlobalNotify и передавая ей в качестве параметра адрес внешней по отношению к приложению функции извещения NotifyProc, расположенной в DLL-библиотеке:
GlobalNotify((GNOTIFYPROC)NotifyProc);
Каждая копия приложения может вызывать функцию GlobalNotify только один раз.
Задача функции извещения заключается в том, чтобы предотвратить удаление блока памяти.
Это сделать очень просто, достаточно того, чтобы функция извещения вернула нулевое значение.
После установки функции извещения приложение заказывает удаляемый блок памяти, указывая среди прочих флаг GMEM_NOTIFY :
hmemGlDiscard = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE | GMEM_NOTIFY, 200000l);
Если этот флаг не будет указан, Windows при необходимости удалит блок без вызова процедуры извещения.
После получения блока памяти приложение пытается его удалить, вызывая функцию GlobalDiscard (можно было бы, конечно, подождать, пока этот блок будет удален самой операционной системой Windows, однако вы можете прождать до утра):
GlobalDiscard(hmemGlDiscard);
Далее приложение пытается зафиксировать блок памяти, чтобы получить и отобразить на экране его логический адрес. Так как наша процедура извещения запрещает удаление блока, попытка фиксирования должна закончится успешно. В этом случае приложение выводит на экран идентификатор и логический адрес блока памяти.
Если же процедура извещения разрешает удаление блока памяти, при попытке фиксирования блока мы получим состояние ошибки. В этом случае приложение восстанавливает и фиксирует блок памяти с выдачей соответствующего сообщения на экран.
Файл определения модуля приложения DISCARD приведен в листинге 3.6.
Файл discard/discard.def
; ============================= ; Файл определения модуля ; ============================= NAME DISCARD DESCRIPTION 'Приложение DISCARD, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Исходный текст DLL-библиотеки приведен в листинге 3.7.
Файл discard/dll.c
#define STRICT #include <windows.h>
// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ========================================================
#pragma argsused int FAR PASCAL LibMain(HINSTANCE hInstance, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать. if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0);
// Возвращаем 1. Это означает, что инициализация // DLL-библиотеки выполнена успешно return 1; }
// ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ========================================================
#pragma argsused int FAR PASCAL WEP(int bSystemExit) { return 1; }
// ======================================================== // Функция NotifyProc // Она получает управление, если в текущей задаче // предпринимается попытка удаления блока памяти. // В этом случае функция возвращает значение 0, // в результате чего Windows отменяет удаление блока // ========================================================
#pragma argsused BOOL FAR PASCAL _export NotifyProc(HGLOBAL hglbl) { return 0; }
Эта библиотека исключительно проста, так как помимо стандартных функций LibMain и WEP в ней определена только одна функция извещения NotifyProc.
Изучение последней также не вызовет ни малейшего затруднения. Функция NotifyProc возвращает значение 0, запрещая Windows удалять блок памяти. Если надо разрешить удаление блока памяти, необходимо вернуть значение 1.
Обратим внимание на файл определения модуля DLL-библиотеки, приведенный в листинге 3.8.
Файл discard/dll.def
; ============================= ; Файл определения модуля ; ============================= LIBRARY DLL DESCRIPTION 'DLL-библиотека DLL, (C) 1994, Frolov A.V.' EXETYPE windows CODE preload fixed DATA preload moveable single HEAPSIZE 1024 EXPORTS NotifyProc @10
Исходя из требований функции извещения, сегмент кода DLL-библиотеки сделан фиксированным. Кроме того, мы использовали оператор EXPORTS для экспортирования функции извещения и задали для этой функции порядковый номер.
Теперь о том, почему для создания DLL-библиотеки мы выбрали язык C, а не С++.
Как вы знаете, трансляторы языка C++ "разукрашивают" имена функций в объектном модуле, добавляя к ним символы, обозначающие типы аргументов и тип возвращаемого значения. Когда в файле определения модуля в разделе EXPORTS мы перечисляем имена функций, при сборке загрузочного файла DLL-библиотеки редактор связей будет искать в объектном модуле функции с перечисленными именами. Если же исходный текст составлен на языке C++, имена, расположенные в объектном модуле, будут мало напоминать использованные в исходном тексте. В результате редактор связей выведет сообщение о том, что он не может найти в объектном модуле указанные в файле определения модуля экспортируемые функции.
Если же исходный текст DLL-библиотеки составлен на языке C, эти проблемы исчезнут. Однако в этом случае при вызове таких экспортируемых функций из приложений, составленных на языке C++, вам придется объявить их как extern"C".
Если же для разработки DLL-библиотеки используется язык C++, для обеспечения доступа к экспортируемым функциям вы можете либо использовать библиотеку импорта, созданную приложением IMPLIB, либо описать экспортируемую функцию следующим образом (в качестве примера использован исходный текст одной из функций DLL-библиотеки, описанной в разделе "Приложение WINHOOK"):
extern "C" void WINAPI _export RemoveKbHook(void) { if(bHooked) { UnhookWindowsHookEx(hhookMsg);
UnhookWindowsHookEx(hhook);
} }
Описание extern "C" отменяет для определяемой функции соглашение языка C++ об именах функций.
Файл winhook/winhook.cpp
// ================================================ // Приложение WINHOOK // Простейший руссификатор клавиатуры // для Microsoft Windows // Работает совместно с DLL-библиотекой kbhook.dll // ================================================
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <dos.h>
#include <mem.h>
#include "kbhook.hpp"
// ---------------------------------------------------- // Прототипы функций // ----------------------------------------------------
extern "C" void WINAPI _export SetKbHook(HWND hwnd);
extern "C" void WINAPI _export RemoveKbHook(void);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// ---------------------------------------------------- // Глобальные переменные // ----------------------------------------------------
char const szClassName[] = "WINHOOKAppClass"; char const szWindowTitle[] = "WINHOOK Application";
TEXTMETRIC tm; int cxChar, cyChar; RECT rc; static BOOL bCyrillic = FALSE;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Можно запускать только одну копию приложения if(hPrevInstance) return 0;
// Инициализация копии приложения if(!InitApp(hInstance)) return FALSE;
// Получаем координаты окна Desktop. // Это окно занимает всю поверхность экрана // и на нем расположены все остальные окна GetWindowRect(GetDesktopWindow(), &rc);
// Создаем временное окно с толстой // рамкой для изменения размера, но без // заголовка и системного меню. // При создании окна указываем произвольные // размеры окна и произвольное расположение hwnd = CreateWindow( szClassName, szWindowTitle, WS_POPUPWINDOW | WS_THICKFRAME, 100, 100, 100, 100, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
// Передвигаем окно в правый нижний // угол экрана и делаем его самым // верхним, т. е. это окно будет // всегда находиться над другими окнами SetWindowPos(hwnd, HWND_TOPMOST, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, 0);
// Отображаем окно в новом месте ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { // Устанавливаем перехватчики SetKbHook(hwnd);
// Получаем контекст отображения hdc = GetDC(hwnd);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);
// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;
// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
return 0; }
// Для обеспечения возможности перемещения // окна, не имеющего заголовка, встраиваем // свой обработчик сообщения WM_NCHITTEST case WM_NCHITTEST: { long lRetVal;
// Вызываем функцию DefWindowProc и проверяем // возвращаемое ей значение lRetVal = DefWindowProc(hwnd, msg, wParam, lParam);
// Если курсор мыши находится на одном из // элементов толстой рамки, предназначенной // для изменения размера окна, возвращаем // неизмененное значение, полученное от // функции DefWindowProc if(lRetVal == HTLEFT lRetVal == HTRIGHT lRetVal == HTTOP lRetVal == HTBOTTOM lRetVal == HTBOTTOMRIGHT lRetVal == HTTOPRIGHT lRetVal == HTTOPLEFT lRetVal == HTBOTTOMLEFT) { return lRetVal; }
// В противном случае возвращаем значение // HTCAPTION, которое соответствует // заголовку окна. else { return HTCAPTION; } }
case WM_DESTROY: { // Перед завершением работы приложения // удаляем перехватчики RemoveKbHook();
PostQuitMessage(0);
return 0; }
// Это сообщение приходит от DLL-библиотеки // при переключении раскладки клавиатуры case WM_KBHOOK: { // Получаем флаг раскладки bCyrillic = (BOOL)wParam;
// Выдаем звуковой сигнал MessageBeep(0);
// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, FALSE);
return 0; }
case WM_PAINT: { BYTE szBuf[10]; RECT rc;
// Получаем контекст отображения hdc = BeginPaint(hwnd, &ps);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Получаем координаты и размер окна GetClientRect(hwnd, &rc);
// В зависимости от состояния флага раскладки // клавиатуры выбираем надпись для // отображения в окне if(bCyrillic) lstrcpy(szBuf, (LPCSTR)"CYRILLIC");
else lstrcpy(szBuf, (LPCSTR)"DEFAULT ");
// Выводим надпись в центре окна DrawText(hdc, (LPSTR)szBuf, 8, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);
EndPaint(hwnd, &ps);
} } return DefWindowProc(hwnd, msg, wParam, lParam);
}
После запуска приложения функция WinMain определяет размеры окна DeskTop, которые равны размеру экрана, и создает главное окно приложения в виде временного окна с толстой рамкой для изменения размера без заголовка и системного меню (такое окно имеет стиль WS_POPUPWINDOW | WS_THICKFRAME).
Для того чтобы сделать окно "непотопляемым", а заодно и изменить его размеры, мы вызываем функцию SetWindowPos , передав ей в качестве второго параметра константу HWND_TOPMOST:
SetWindowPos(hwnd, HWND_TOPMOST, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, 0);
Функция SetWindowPos позволяет изменить размеры и расположение окна относительно экрана и относительно других окон:
BOOL WINAPI SetWindowPos( HWND hwnd, // идентификатор окна HWND hwndInsertAfter, // расположение окна int x, // горизонтальное положение int y, // вертикальное положение int cx, // ширина int cy, // высота UINT fuFlags);
// флаги расположения окна
Для параметра hwndInsertAfter, определяющего расположение окна относительно других окон, можно использовать следующие значения:
Значение | Описание |
HWND_BOTTOM | Окно следует расположить под другими окнами |
HWND_TOP | Окно будет расположено над другими окнами |
HWND_TOPMOST | Окно следует расположить над всеми другими окнами, имеющими расположение HWND_TOPMOST |
HWND_NOTOPMOST | Окно будет расположено над всеми HWND_TOP-окнами, но под окном, имеющим расположение HWND_TOPMOST |
Параметр fuFlags может принимать следующие значения:
Значение | Описание |
SWP_DRAWFRAME | Следует нарисовать рамку, определенную в классе окна |
SWP_HIDEWINDOW | Окно будет скрыто |
SWP_NOACTIVATE | Окно не будет активизировано |
SWP_NOMOVE | Окно не будет перемещаться, при указании этого флага параметры x и y игнорируются |
SWP_NOSIZE | Окно не будет изменять свои размеры, параметры cx и cy игнорируются |
SWP_NOREDRAW | Не следует выполнять перерисовку окна. После перемещения приложение должно перерисовать окно самостоятельно |
SWP_NOZORDER | Не следует изменять расположение окна относительно других окон, параметр hwndInsertAfter игнорируется |
SWP_SHOWWINDOW | Отобразить окно |
Функция главного окна приложения во время обработки сообщения WM_CREATE вызывает функцию SetKbHook, которая определена в созданной нами для этого приложения DLL-библиотеке kbhook.dll (исходные тексты этой библиотеки будут приведены ниже). Функция SetKbHook устанавливает два фильтра - типа WH_KEYBOARD и WH_GETMESSAGE. В качестве параметра этой функции передается идентификатор главного окна приложения WINHOOK. Когда пользователь переключает раскладку клавиатуры, DLL-библиотека, пользуясь этим идентификатором, пришлет в функцию окна приложения WINHOOK сообщение с кодом WM_KBHOOK.
Затем обработчик сообщения WM_CREATE получает контекст отображения и определяет метрику системного шрифта с фиксированной шириной букв, который будет использоваться для отображения используемой раскладки клавиатуры в главном окне приложения.
В функции главного окна предусмотрен обработчик сообщения WM_NCHITTEST, позволяющий перемещать окно, не имеющее заголовка. Мы уже использовали аналогичный прием в приложении TMCLOCK, описанном в 11 томе "Библиотеки системного программиста".
Перед завершением работы приложение WINHOOK удаляет установленные ранее фильтры, для чего при обработке сообщения WM_DESTROY вызывается функция RemoveKbHook, определенная в DLL-библиотеке kbhook.dll.
Обработчик сообщения WM_KBHOOK, определенного в нашем приложении и в DLL-библиотеке, определяет номер используемой раскладки клавиатуры через параметр wParam. Полученное значение записывается в переменную флагов раскладки bCyrillic. Если этот флаг сброшен, используется стандартная раскладка клавиатуры, если установлен - дополнительная с символами кириллицы.
После определения раскладки обработчик сообщения WM_KBHOOK выдает звуковой сигнал и перерисовывает главное окно приложения, вызывая функцию InvalidateRect.
Перерисовку окна выполняет обработчик сообщения WM_PAINT. С помощью функции DrawText он пишет название раскладки клавиатуры (DEFAULT или CYRILLIC) в центре главного окна приложения.
Сообщение WM_KBHOOK определено в include-файле winhook.hpp (листинг 3.10).
Файл winhook/winhook.hpp
#define WM_KBHOOK (WM_USER + 1000)
Код этого сообщения получается при помощи сложения константы WM_USER и числа 1000 (вы можете выбрать другое число в диапазоне от 0 до 0x7fff).
Константа WM_USER специально предназначена для определения приложениями своих собственных кодов сообщений. Если приложение определит свое собственное сообщение и его код будет находиться в пределах от WM_USER до WM_USER + 0x7fff, код такого сообщения не будет конфликтовать с кодами сообщений операционной системы Windows.
Файл определения модуля приложения WINHOOK приведен в листинге 3.11.
Файл winhook/winhook.def
; ============================= ; Файл определения модуля ; ============================= NAME WINHOOK DESCRIPTION 'Приложение WINHOOK, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Теперь займемся DLL-библиотекой kbhook.dll, предназначенной для совместной работы с приложением WINHOOK. Исходный текст этой библиотеки представлен в листинге 3.12.
Файл winhook/kbhook.cpp
// ================================================ // DLL-библиотека kbhook.dll // Устанавливает перехватчики на сообщения, // поступающие от клавиатуры и на системную // очередь сообщений. // Если нажать подряд 3 раза клавишу <Control>
, // изменится раскладка клавиатуры // ================================================
#define STRICT #include <windows.h>
#include "kbhook.hpp"
// ----------------------------------------------- // Глобальные переменные // -----------------------------------------------
// Идентификатор модуля DLL-библиотеки static HINSTANCE hInst;
// Идентификатор окна приложения, установившего // перехватчики static HWND hwndClient;
// Идентификаторы перехватчиков static HHOOK hhook = 0; static HHOOK hhookMsg = 0;
// Флаг переключения на русскую клавиатуру static BOOL bCyrillic = FALSE;
// Флаг установки перехватчиков static BOOL bHooked = FALSE;
// Счетчик static int nHotKeyCount = 0;
// Массив для записи состояния клавиатуры BYTE aKeyStates[256];
// Указатели на таблицы перекодировки, // которые будут загружены из ресурсов static char far * lpXlatTable; static char far * lpXlatTableCaps;
// Положение ресурсов в файле static HRSRC hResource; static HRSRC hResourceCaps;
// Идентификаторы таблиц перекодировки static HGLOBAL hXlatTable; static HGLOBAL hXlatTableCaps;
// ----------------------------------------------- // Прототипы функций // -----------------------------------------------
extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam);
extern "C" LRESULT CALLBACK MsgHookProc(int code, WPARAM wParam, LPARAM lParam);
// ======================================================== // Функция LibMain // Получает управление только один раз при // загрузке DLL-библиотеки в память // ========================================================
#pragma argsused int CALLBACK LibMain(HINSTANCE hInstance, WORD wDataSegment, WORD wHeapSize, LPSTR lpszCmdLine) { // После инициализации локальной области данных // функция LibEntry фиксирует сегмент данных. // Его необходимо расфиксировать.
if(wHeapSize != 0) // Расфиксируем сегмент данных UnlockData(0);
// Запоминаем идентификатор модуля DLL-библиотеки hInst = hInstance;
// Определяем расположение ресурсов hResource = FindResource(hInstance, "XlatTable", "XLAT");
hResourceCaps = FindResource(hInstance, "XlatTableCaps", "XLAT");
// Получаем идентификаторы ресурсов hXlatTable = LoadResource(hInstance, hResource);
hXlatTableCaps = LoadResource(hInstance, hResourceCaps);
// Фиксируем ресурсы в памяти, получая их адрес lpXlatTable = (char far *)LockResource(hXlatTable);
lpXlatTableCaps = (char far *)LockResource(hXlatTableCaps);
// Если адрес равен NULL, при загрузке или // фиксации одного из ресурсов произошла ошибка if(lpXlatTable == NULL lpXlatTableCaps == NULL) { return(0);
}
// Выключаем клавишу <Caps Lock>
. Для этого // получаем и записываем в массив aKeyStates состояние // клавиатуры, затем изменяем состояние нужной нам // клавиши, используя ее виртуальный код как индекс GetKeyboardState(aKeyStates);
aKeyStates[VK_CAPITAL] = 0; SetKeyboardState(aKeyStates);
// Возвращаем 1. Это означает, что инициализация // DLL-библиотеки выполнена успешно return 1; }
// ======================================================== // Функция WEP // Получает управление только один раз при // удалении DLL-библиотеки из памяти // ========================================================
#pragma argsused int CALLBACK WEP(int bSystemExit) { // Расфиксируем и освобождаем ресурсы UnlockResource(hXlatTable);
FreeResource(hXlatTable);
UnlockResource(hXlatTableCaps);
FreeResource(hXlatTableCaps);
return 1; }
// ======================================================== // Функция SetKbHook // Устанавливает системные перехватчики на сообщения, // поступающие от клавиатуры, и на системную // очередь сообщений // ========================================================
extern "C" void WINAPI _export SetKbHook(HWND hwnd) { // Устанавливаем перехватчики только // в том случае, если они не были // установлены ранее if(!bHooked) { // Установка перехватчика на сообщения, // поступающие от клавиатуры hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL);
// Установка перехватчика на системную // очередь сообщений hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MsgHookProc, hInst, NULL);
// Включаем флаг установки перехватчиков bHooked = TRUE;
// Сохраняем идентификатор окна приложения, // установившего перехватчики hwndClient = hwnd; } }
// ======================================================== // Функция RemoveKbHook // Удаляет системные перехватчики // ========================================================
extern "C" void WINAPI _export RemoveKbHook(void) { // Если перехватчики были установлены, // удаляем их if(bHooked) { UnhookWindowsHookEx(hhookMsg);
UnhookWindowsHookEx(hhook);
} }
// ======================================================== // Функция KbHookProc // Перехватчик сообщений от клавиатуры // ========================================================
extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam) { // Проверка флага обработки сообщений. Если содержимое // параметра code меньше нуля, передаем сообщение // функции CallNextHookEx без изменений if(code < 0) { CallNextHookEx(hhook, code, wParam, lParam);
return 0; }
// Если пришло сообщение от клавиши <Control>
, // проверяем, была ли эта клавиша нажата // три раза подряд if(wParam == VK_CONTROL) { if(!(HIWORD(lParam) & 0x8000)) { nHotKeyCount++;
// Если клавиша <Control>
была нажата три // раза подряд, инвертируем флаг bCyrillic и посылаем // сообщение приложению, использующему // данную DLL-библиотеку if(nHotKeyCount == 3) { nHotKeyCount = 0; bCyrillic = ~bCyrillic;
// Посылаем сообщение приложению, установившему // перехватчики. В качестве параметра wParam // сообщения передаем значение флага bCyrillic PostMessage(hwndClient, WM_KBHOOK, (WPARAM)bCyrillic, 0L);
} } }
// Если после клавиши <Control>
была нажата любая // другая клавиша, сбрасываем счетчик else { nHotKeyCount = 0; }
// Вызываем следующий в цепочке перехватчик return CallNextHookEx(hhook, code, wParam, lParam);
}
// ======================================================== // Функция MsgHookProc // Перехватчик для системной очереди сообщений // ========================================================
extern "C" LRESULT CALLBACK MsgHookProc(int code, WPARAM wParam, LPARAM lParam) { LPMSG lpmsg; WPARAM wMsgParam;
// Проверка флага обработки сообщений. Если содержимое // параметра code меньше нуля, передаем сообщение // функции CallNextHookEx без изменений if(code < 0) { CallNextHookEx(hhook, code, wParam, lParam);
return 0; }
// Получаем указатель на структуру MSG, // в которой находится перехваченное сообщение lpmsg = (LPMSG)lParam;
// Запоминаем виртуальный код клавиши wMsgParam = lpmsg->
wParam;
// Сбрасываем флаг замены сообщения // WM_KEYDOWN на сообщение WM_CHAR BOOL bChange = FALSE;
// Если перехвачено сообщение WM_KEYDOWN, // проверяем код виртуальной клавиши и при // необходимости выполняем замену сообщения if(lpmsg->
message == WM_KEYDOWN) { // Замена выполняется только в режиме // русской клавиатуры if(bCyrillic) { // Если нажата клавиша, соответствующая // русской букве, включаем флаг bChange switch(wMsgParam) { // Проверяем "особые" буквы case 0xdb: // "Х" case 0xdd: // "Ъ" case 0xba: // "Ж" case 0xde: // "Э" case 0xbc: // "Б" case 0xbe: // "Ю" case 0xbf: // "Ў" { bChange = TRUE; break; }
// Проверяем остальные буквы default: { if((lpmsg->
wParam <= 0x5d && lpmsg->
wParam >
0x2f)) bChange = TRUE; } }
// Если нажата клавиша, соответствующая русской // букве, выполняем замену сообщения WM_KEYDOWN на // сообщение WM_CHAR if(bChange) { // Делаем замену кода сообщения lpmsg->
message = WM_CHAR;
// Необходимо учитывать состояние клавиш // <Caps Lock>
и <Shift>
if(GetKeyState(VK_CAPITAL) & 0x1) { if(GetKeyState(VK_SHIFT) & 0x8000) { // Перекодировка по таблице строчных букв lpmsg->
wParam = lpXlatTable[(lpmsg->
wParam) & 0xff]; } else { // Перекодировка по таблице прописных букв lpmsg->
wParam = lpXlatTableCaps[(lpmsg->
wParam) & 0xff]; } } else { if(GetKeyState(VK_SHIFT) & 0x8000) { // Перекодировка по таблице прописных букв lpmsg->
wParam = lpXlatTableCaps[(lpmsg->
wParam) & 0xff]; } else { // Перекодировка по таблице строчных букв lpmsg->
wParam = lpXlatTable[(lpmsg->
wParam) & 0xff]; } }
// Сбрасываем флаг замены bChange = FALSE; } } }
// Передаем управление следующему в цепочке // перехватчику сообщений CallNextHookEx(hhook, code, wParam, lParam);
return 0; }
Функция LibMain, выполняющая инициализацию DLL-библиотеки, после инициализации сегмента данных, запоминает идентификатор модуля DLL-библиотеки, переданный ей через параметр hInstance, в глобальной переменной hinst.
Затем эта функция находит, загружает в память и фиксирует две таблицы, необходимые для использования дополнительной раскладки клавиатуры. Первая таблица загружается из ресурса "XlatTable". Она содержит таблицу перекодировки кодов виртуальных клавиш в ANSI-коды русских строчных букв. Вторая таблица загружается из ресурса "XlatTableCaps" и содержит таблицу перекодировки кодов виртуальных клавиш в ANSI-коды русских прописных букв.
После загрузки и фиксирования адреса таблиц записываются в глобальные переменные с именами lpXlatTable и lpXlatTableCaps.
Так как перекодировка кодов виртуальных клавиш в ANSI-коды должна выполняться с учетом состояния клавиши <Caps Lock>
, для упрощения перекодировки функция LibMain устанавливает эту клавишу в выключенное состояние, пользуясь функциями GetKeyboardState и SetKeyboardState :
GetKeyboardState(aKeyStates);
aKeyStates[VK_CAPITAL] = 0; SetKeyboardState(aKeyStates);
Указанные функции, а также использованный способ изменения состояния клавиш был описан в 11 томе "Библиотеки системного программиста".
Функция WEP выполняет расфиксирование и освобождение ресурсов, вызывая функции UnlockResource и FreeResource.
Для установки фильтров в нашей DLL-библиотеки определена функция SetKbHook:
extern "C" void WINAPI _export SetKbHook(HWND hwnd) { if(!bHooked) { hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL);
hhookMsg = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)MsgHookProc, hInst, NULL);
bHooked = TRUE; hwndClient = hwnd; }
При определении этой функции мы использовали ключевые слова extern "C", в результате чего транслятор C++ не выполняет преобразование имени функции в соответствии с типом ее параметров и типом возвращаемого значения.
Перед установкой фильтров функция проверяет содержимое глобального флага bHooked. Этот флаг изначально находится в сброшенном состоянии и устанавливается только после встраивания фильтров. Проверка флага позволяет исключить ошибочное повторное встраивание фильтров.
Функция SetKbHook устанавливает два фильтра - типа WH_KEYBOARD и типа WH_GETMESSAGE. Оба фильтра встраиваются для всей системы в целом, так как последний параметр функции SetWindowsHookEx указан как NULL.
После встраивания фильтров устанавливается флаг bHooked, в глобальную переменную hwndClient записывается идентификатор окна, переданного функции SetKbHook в качестве параметра. Приложение WINHOOK передает идентификатор своего главного окна, пользуясь которым фильтр, расположенный в DLL-библиотеке, будет посылать приложению WINHOOK сообщение WM_KBHOOK.
Для удаления фильтров в DLL-библиотеке определена функция RemoveKbHook:
extern "C" void WINAPI _export RemoveKbHook(void) { if(bHooked) { UnhookWindowsHookEx(hhookMsg);
UnhookWindowsHookEx(hhook);
} }
Эта функция проверяет состояние флага bHooked, и, если фильтры были установлены, удаляет их, вызывая для каждого фильтра функцию UnhookWindowsHookEx.
Функция фильтра типа WH_KEYBOARD пропускает через себя все клавиатурные сообщения, определяя момент, когда пользователь нажмет подряд три раза клавишу <Control>
.
Как только это произошло, функция фильтра инвертирует флаг bCyrillic и посылает приложению WINHOOK сообщение WM_KBHOOK. В качестве параметра wParam посылаемого сообщения используется значение флага bCyrillic, что позволяет определить приложению номер используемой раскладки клавиатуры:
PostMessage(hwndClient, WM_KBHOOK, (WPARAM)bCyrillic, 0L);
Фильтр типа WH_MSGFILTER расположен в функции MsgHookProc.
Если перехвачено сообщение WM_KEYDOWN, фильтр проверяет номер используемой раскладки клавиатуры. Если используется стандартная раскладка клавиатуры, сообщение "пропускается" через фильтр без изменений. Для дополнительной раскладки клавиатуры выполняется проверка кода виртуальной клавиши.
Если этот код соответствует клавишам русских букв или цифр в верхнем ряду клавиатуры, фильтр преобразует сообщение WM_KEYDOWN в сообщение WM_CHAR, пользуясь таблицами перекодировок, загруженным из ресурсов DLL-библиотеки. При этом учитывается состояние клавиш <Shift>
и <Caps Lock>
. Для определения состояния переключающих клавиш мы использовали функцию GetKeyState.
Таблицы перекодировки для прописных и строчных букв описаны в файле ресурсов DLL-библиотеки (листинг 3.13).