Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 1.12. Приложение SMARTPADДля иллюстрации всего сказанного выше относительно использования меню и органа управления Toolbar мы приведем исходные тексты приложения SMARTPAD, которое реализует основные функции, связанные с редактированием текста (рис. 1.19).
Рис. 1.19. Главное окно приложения SMARTPAD Так как мы пока еще не умеем работать с принтерами и шрифтами, первая версия нашего редактора текста несколько ограничена. Кроме того, пока не реализованы функции меню "Help". Тем не менее это приложение имеет Toolbar, использует акселераторы для доступа к основным функциям, модифицирует системное меню и по щелчку правой клавиши мыши создает в окне редактирования плавающее меню. На примере этого приложения мы демонстрируем не только способы работы с различными типами меню, но и способ "перехвата" управления у функции окна органа управления класса "edit". Орган управления Toolbar выделен в отдельный класс (в терминах языка программирования С++), исходные тексты которого находятся в двух файлах. Вы сможете с помощью этого класса без труда (почти) создавать в приложениях свой собственный Toolbar. Основной файл приложения SMARTPAD приведен в листинге 1.8. Листинг 1.8. Файл 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). Листинг 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) описаны ресурсы приложения. Листинг 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, на которые есть ссылки в файле описания ресурсов. Листинг 1.11. Файлы smartpad/*.bmp
Определение класса Toolbar, предназначенного для создания органа управления Toolbar, находится в файле toolbar.hpp (листинг 1.12). Листинг 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. Листинг 1.13. Файл 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). Мы уже пользовались этой функцией. Листинг 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. Листинг 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 |